diff --git a/.gitignore b/.gitignore index 64e3509..b213f95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,35 +1,153 @@ -# Eclipse stuff -/.classpath -/.project -/.settings +### Eclipse ### +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath -# netbeans -/nbproject +# Eclipse Core +.project -# we use maven! -/build.xml +# External tool builders +.externalToolBuilders/ -# maven -/target +# Locally stored "Eclipse launch configurations" +*.launch -# vim -.*.sw[a-p] +# CDT-specific +.cproject -# various other potential build files -/build -/bin -/dist -/manifest.mf +# JDT-specific (Eclipse Java Development Tools) +.classpath -# Mac filesystem dust -/.DS_Store +# PDT-specific +.buildpath -# intellij +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse + + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +nbactions.xml +nb-configuration.xml +.nb-gradle/ + + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm + *.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: *.ipr *.iws -.idea/ -/dependency-reduced-pom.xml -/*.pack -/local_launcher.properties \ No newline at end of file +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties + + +### Java ### +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties + + +### Gradle ### +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk diff --git a/README.md b/README.md index 77e258d..46f7f5f 100644 --- a/README.md +++ b/README.md @@ -4,73 +4,56 @@ This project provides an open-source Minecraft launcher platform for downloading, installing, and updating modpacks. -Introduction ------------- +The launcher has its roots in MC Alpha as a launcher for sk89q's server. -This launcher is maintained by sk89q, who writes WorldEdit, WorldGuard, and so on. It has -been primarily developed for his server, but you can use it for your own modpack or -server. +**Note:** "SKMCLauncher" is the *older* version of this launcher. This project is called "SKCraft Launcher." -* One of Minecraft's oldest launchers -- since Minecraft Alpha +## Introduction + * Requires almost no configuration files to make a modpack * Add a new mod by dropping in the .jar (and its configuration) * Remove a mod by deleting its .jar (and configuration). * Builds **server** modpacks with no extra configuration * Advanced download system: incremental, file removal detection, optional feature/mod selection, etc. * Very easy for users to use and install modpacks -* Pretty well-documented with easy-to-understand, well-organized code* * Open source! -*Except for the Launcher frame class. That one is pretty bad. +## Usage -### Previous Versions - -This repository only contains code for the launcher versions 4.x and newer. - -You can find [the 3.x version on GitHub](https://github.com/sk89q/skmclauncher). - -Documentation -------------- - -First off, be aware that the launcher in this directory has been branded for sk89q's -server, so you will have to replace that with your own. There's only a few places that -you need to do that, and it's all documented on our documentation page. - -**You can fork the project on GitHub** and make modifications. +1. Download the code. +2. See if you can compile it (see instructions below). +3. Read the documentation to (1) learn how to change the launcher to use your own website and (2) create modpacks in the right format for the launcher. * [Documentation](http://wiki.sk89q.com/wiki/Launcher) +* [Forum to ask for help](http://forum.enginehub.org/forums/launcher.25/) -Note that documentation may be lacking in some places. If you run into problems, -**do not hesitate to ask**. +You can also [contact sk89q](http://www.sk89q.com/contact/). -If you want to contact me about some sort of partnership, want to make the launcher -the official launcher for something, please email me (see -[my website](http://www.sk89q.com/contact/)). While you do not have to do this, we -can make future decisions with awareness of what the needs of other users may be. +## Compiling -Compiling ---------- +First, make sure to install the Java Development Kit (JDK). -The launcher can be compiled using [Maven](http://maven.apache.org/). +In your command prompt or terminal, run: - mvn clean package + ./gradlew clean build -If you wish to import the project into an IDE, you must add support for -Project Lombok. +If you are on Windows: -Contributing ------------- + gradlew clean build -Pull requests can be submitted on GitHub, but we will accept them -at our discretion. Please note that your code must follow -Oracle's Java Code Conventions. +Once compiled, look for the "-all" .jar files in the following folders: -Contributions by third parties must be dual licensed under the two licenses -described within LICENSE.txt (GNU General Public License, version 3, and the -3-clause BSD license). +* `launcher/build/libs/` - The main launcher +* `launcher-builder/build/libs/` - Command line app to build modpacks +If you wish to import the project into an IDE, you must add support for Project Lombok (IntelliJ IDEA users: also enable annotation processing in compiler settings). -License -------- +## Contributing + +Pull requests can be submitted on GitHub, but we will accept them at our discretion. Please note that your code must follow Oracle's Java Code Conventions. + +Contributions by third parties must be dual licensed under the two licenses described within LICENSE.txt (GNU General Public License, version 3, and the 3-clause BSD license). + +## License The launcher is licensed under the GNU General Public License, version 3. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..ce16d93 --- /dev/null +++ b/build.gradle @@ -0,0 +1,41 @@ +println """ +******************************************* + You are building SKCraft Launcher! + + Output files will be in [subproject]/build/libs +******************************************* +""" + +buildscript { + repositories { + mavenCentral() + jcenter() + } + + dependencies { + classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.0' + } +} + +subprojects { + apply plugin: 'java' + apply plugin: 'maven' + + group = 'com.skcraft' + version = '4.2.3-SNAPSHOT' + + sourceCompatibility = 1.6 + targetCompatibility = 1.6 + + repositories { + mavenCentral() + maven { url "http://repo.maven.apache.org/maven2" } + } + + if (JavaVersion.current().isJava8Compatible()) { + // Java 8 turns on doclint which we fail + tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:none', '-quiet') + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..3d0dee6 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar Binary files differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..b9c1aac --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Feb 18 18:57:36 PST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/launcher-builder/build.gradle b/launcher-builder/build.gradle new file mode 100644 index 0000000..828447c --- /dev/null +++ b/launcher-builder/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'com.github.johnrengelman.shadow' + +jar { + manifest { + attributes("Main-Class": "com.skcraft.launcher.builder.PackageBuilder") + } +} + +dependencies { + compile project(':launcher') + compile 'org.tukaani:xz:1.0' + compile 'org.apache.commons:commons-compress:1.9' +} + +shadowJar { + dependencies { + exclude(dependency('org.projectlombok:lombok')) + } +} + +build.dependsOn(shadowJar) \ No newline at end of file diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderConfig.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderConfig.java new file mode 100644 index 0000000..658d619 --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderConfig.java @@ -0,0 +1,49 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.skcraft.launcher.model.modpack.LaunchModifier; +import com.skcraft.launcher.model.modpack.Manifest; +import lombok.Data; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.emptyToNull; + +@Data +public class BuilderConfig { + + private String name; + private String title; + private String gameVersion; + @JsonProperty("launch") + private LaunchModifier launchModifier; + private List features; + private FnPatternList userFiles; + + public void update(Manifest manifest) { + manifest.updateName(getName()); + manifest.updateTitle(getTitle()); + manifest.updateGameVersion(getGameVersion()); + manifest.setLaunchModifier(getLaunchModifier()); + } + + public void registerProperties(PropertiesApplicator applicator) { + if (features != null) { + for (FeaturePattern feature : features) { + checkNotNull(emptyToNull(feature.getFeature().getName()), + "Empty feature name found"); + applicator.register(feature); + } + } + + applicator.setUserFiles(userFiles); + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderOptions.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderOptions.java new file mode 100644 index 0000000..793312e --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderOptions.java @@ -0,0 +1,109 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.ParameterException; +import lombok.Data; + +import java.io.File; + +@Data +public class BuilderOptions { + + // Configuration + + // Override config + @Parameter(names = "--name") + private String name; + @Parameter(names = "--title") + private String title; + @Parameter(names = "--mc-version") + private String gameVersion; + + // Required + @Parameter(names = "--version", required = true) + private String version; + @Parameter(names = "--manifest-dest", required = true) + private File manifestPath; + + // Overall paths + @Parameter(names = {"--input", "-i"}) + private File inputPath; + @Parameter(names = {"--output", "-o"}) + private File outputPath; + + // Input paths + @Parameter(names = "--config") + private File configPath; + @Parameter(names = "--version-file") + private File versionManifestPath; + @Parameter(names = "--files") + private File filesDir; + @Parameter(names = "--loaders") + private File loadersDir; + + // Output paths + @Parameter(names = "--objects-dest") + private File objectsDir; + @Parameter(names = "--libraries-dest") + private File librariesDir; + + @Parameter(names = "--libs-url") + private String librariesLocation = "libraries"; + @Parameter(names = "--objects-url") + private String objectsLocation = "objects"; + + // Misc + @Parameter(names = "--pretty-print") + private boolean prettyPrinting; + + public void choosePaths() throws ParameterException { + if (configPath == null) { + requireInputPath("--config"); + configPath = new File(inputPath, "modpack.json"); + } + + if (versionManifestPath == null) { + requireInputPath("--version"); + versionManifestPath = new File(inputPath, "version.json"); + } + + if (filesDir == null) { + requireInputPath("--files"); + filesDir = new File(inputPath, "src"); + } + + if (loadersDir == null) { + requireInputPath("--loaders"); + loadersDir = new File(inputPath, "loaders"); + } + + if (objectsDir == null) { + requireOutputPath("--objects-dest"); + objectsDir = new File(outputPath, objectsLocation); + } + + if (librariesDir == null) { + requireOutputPath("--libs-dest"); + librariesDir = new File(outputPath, librariesLocation); + } + } + + private void requireOutputPath(String name) throws ParameterException { + if (outputPath == null) { + throw new ParameterException("Because " + name + " was not specified, --output needs to be specified as the output directory and then " + name + " will be default to a pre-set path within the output directory"); + } + } + + private void requireInputPath(String name) throws ParameterException { + if (inputPath == null) { + throw new ParameterException("Because " + name + " was not specified, --input needs to be specified as the project directory and then " + name + " will be default to a pre-set path within the project directory"); + } + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java new file mode 100644 index 0000000..8f2e722 --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java @@ -0,0 +1,52 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.beust.jcommander.internal.Lists; +import org.apache.commons.compress.compressors.CompressorStreamFactory; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public final class BuilderUtils { + + private BuilderUtils() { + } + + public static String normalizePath(String path) { + return path.replaceAll("^[/\\\\]*", "").replaceAll("[/\\\\]+", "/"); + } + + public static ZipEntry getZipEntry(ZipFile jarFile, String path) { + Enumeration entries = jarFile.entries(); + String expected = normalizePath(path); + + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String test = normalizePath(entry.getName()); + if (expected.equals(test)) { + return entry; + } + } + + return null; + } + + public static List getCompressors(String repoUrl) { + if (repoUrl.matches("^https?://files.minecraftforge.net/maven/")) { + return Lists.newArrayList( + new Compressor("xz", CompressorStreamFactory.XZ), + new Compressor("pack", CompressorStreamFactory.PACK200)); + } else { + return Collections.emptyList(); + } + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/ClientFileCollector.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/ClientFileCollector.java new file mode 100644 index 0000000..de8862a --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/ClientFileCollector.java @@ -0,0 +1,99 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import com.google.common.io.Files; +import com.skcraft.launcher.model.modpack.FileInstall; +import com.skcraft.launcher.model.modpack.Manifest; +import lombok.NonNull; +import lombok.extern.java.Log; +import org.apache.commons.io.FilenameUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * Walks a path and adds hashed path versions to the given + * {@link com.skcraft.launcher.model.modpack.Manifest}. + */ +@Log +public class ClientFileCollector extends DirectoryWalker { + + public static final String URL_FILE_SUFFIX = ".url.txt"; + + private final Manifest manifest; + private final PropertiesApplicator applicator; + private final File destDir; + private HashFunction hf = Hashing.sha1(); + + /** + * Create a new collector. + * + * @param manifest the manifest + * @param applicator applies properties to manifest entries + * @param destDir the destination directory to copy the hashed objects + */ + public ClientFileCollector(@NonNull Manifest manifest, @NonNull PropertiesApplicator applicator, + @NonNull File destDir) { + this.manifest = manifest; + this.applicator = applicator; + this.destDir = destDir; + } + + @Override + protected DirectoryBehavior getBehavior(@NonNull String name) { + return getDirectoryBehavior(name); + } + + @Override + protected void onFile(File file, String relPath) throws IOException { + if (file.getName().endsWith(FileInfoScanner.FILE_SUFFIX) || file.getName().endsWith(URL_FILE_SUFFIX)) { + return; + } + + // url.txt override file + File urlFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + URL_FILE_SUFFIX); + String to; + if (urlFile.exists()) { + to = Files.readFirstLine(urlFile, Charset.defaultCharset()); + } else { + to = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(relPath)); + } + + FileInstall entry = new FileInstall(); + String hash = Files.hash(file, hf).toString(); + String hashedPath = hash.substring(0, 2) + "/" + hash.substring(2, 4) + "/" + hash; + File destPath = new File(destDir, hashedPath); + entry.setHash(hash); + entry.setLocation(hashedPath); + entry.setTo(to); + entry.setSize(file.length()); + applicator.apply(entry); + destPath.getParentFile().mkdirs(); + ClientFileCollector.log.info(String.format("Adding %s from %s...", relPath, file.getAbsolutePath())); + Files.copy(file, destPath); + manifest.getTasks().add(entry); + } + + public static DirectoryBehavior getDirectoryBehavior(@NonNull String name) { + if (name.startsWith(".")) { + return DirectoryBehavior.SKIP; + } else if (name.equals("_OPTIONAL")) { + return DirectoryBehavior.IGNORE; + } else if (name.equals("_SERVER")) { + return DirectoryBehavior.SKIP; + } else if (name.equals("_CLIENT")) { + return DirectoryBehavior.IGNORE; + } else { + return DirectoryBehavior.CONTINUE; + } + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/Compressor.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/Compressor.java new file mode 100644 index 0000000..40958b1 --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/Compressor.java @@ -0,0 +1,48 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import org.apache.commons.compress.compressors.CompressorException; +import org.apache.commons.compress.compressors.CompressorStreamFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class Compressor { + + private static final CompressorStreamFactory factory = new CompressorStreamFactory(); + + private final String extension; + private final String format; + + public Compressor(String extension, String format) { + this.extension = extension; + this.format = format; + } + + public String transformPathname(String filename) { + return filename + "." + extension; + } + + public InputStream createInputStream(InputStream inputStream) throws IOException { + try { + return factory.createCompressorInputStream(format, inputStream); + } catch (CompressorException e) { + throw new IOException("Failed to create decompressor", e); + } + } + + public OutputStream createOutputStream(OutputStream outputStream) throws IOException { + try { + return factory.createCompressorOutputStream(format, outputStream); + } catch (CompressorException e) { + throw new IOException("Failed to create compressor", e); + } + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/DirectoryWalker.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/DirectoryWalker.java new file mode 100644 index 0000000..f92726c --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/DirectoryWalker.java @@ -0,0 +1,99 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import lombok.NonNull; + +import java.io.File; +import java.io.IOException; + +/** + * Abstract class to recursively walk a directory, keep track of a relative + * path (which may be modified by dropping certain directory entries), + * and call {@link #onFile(java.io.File, String)} with each file. + */ +public abstract class DirectoryWalker { + + public enum DirectoryBehavior { + /** + * Continue and add the given directory to the relative path. + */ + CONTINUE, + /** + * Continue but don't add the given directory to the relative path. + */ + IGNORE, + /** + * Don't walk this directory. + */ + SKIP + } + + /** + * Walk the given directory. + * + * @param dir the directory + * @throws IOException thrown on I/O error + */ + public final void walk(@NonNull File dir) throws IOException { + walk(dir, ""); + } + + /** + * Recursively walk the given directory and keep track of the relative path. + * + * @param dir the directory + * @param basePath the base path + * @throws IOException + */ + private void walk(@NonNull File dir, @NonNull String basePath) throws IOException { + if (!dir.isDirectory()) { + throw new IllegalArgumentException(dir.getAbsolutePath() + " is not a directory"); + } + + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + String newPath = basePath; + + switch (getBehavior(file.getName())) { + case CONTINUE: + newPath += file.getName() + "/"; + case IGNORE: + walk(file, newPath); + break; + case SKIP: break; + } + } else { + onFile(file, basePath + file.getName()); + } + } + } + } + + /** + * Return the behavior for the given directory name. + * + * @param name the directory name + * @return the behavor + */ + protected DirectoryBehavior getBehavior(String name) { + return DirectoryBehavior.CONTINUE; + } + + /** + * Callback on each file. + * + * @param file the file + * @param relPath the relative path + * @throws IOException thrown on I/O error + */ + protected abstract void onFile(File file, String relPath) throws IOException; + + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/FeaturePattern.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/FeaturePattern.java new file mode 100644 index 0000000..01cbf93 --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/FeaturePattern.java @@ -0,0 +1,24 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.skcraft.launcher.model.modpack.Feature; +import lombok.Data; + +@Data +public class FeaturePattern { + + @JsonProperty("properties") + private Feature feature; + @JsonProperty("files") + private FnPatternList filePatterns; + + public boolean matches(String path) { + return filePatterns != null && filePatterns.matches(path); + } +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/FileInfo.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/FileInfo.java new file mode 100644 index 0000000..bea808a --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/FileInfo.java @@ -0,0 +1,17 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.skcraft.launcher.model.modpack.Feature; +import lombok.Data; + +@Data +public class FileInfo { + + private Feature feature; + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/FileInfoScanner.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/FileInfoScanner.java new file mode 100644 index 0000000..bd1c1b7 --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/FileInfoScanner.java @@ -0,0 +1,75 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.skcraft.launcher.model.modpack.Feature; +import lombok.Getter; +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.emptyToNull; +import static com.skcraft.launcher.builder.ClientFileCollector.getDirectoryBehavior; +import static org.apache.commons.io.FilenameUtils.*; + +@Log +public class FileInfoScanner extends DirectoryWalker { + + private static final EnumSet MATCH_FLAGS = EnumSet.of( + FnMatch.Flag.CASEFOLD, FnMatch.Flag.PERIOD, FnMatch.Flag.PATHNAME); + public static final String FILE_SUFFIX = ".info.json"; + + private final ObjectMapper mapper; + @Getter + private final List patterns = new ArrayList(); + + public FileInfoScanner(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + protected DirectoryBehavior getBehavior(String name) { + return getDirectoryBehavior(name); + } + + @Override + protected void onFile(File file, String relPath) throws IOException { + if (file.getName().endsWith(FILE_SUFFIX)) { + String fnPattern = + separatorsToUnix(getPath(relPath)) + + getBaseName(getBaseName(file.getName())) + "*"; + + FileInfo info = mapper.readValue(file, FileInfo.class); + Feature feature = info.getFeature(); + + if (feature != null) { + checkNotNull(emptyToNull(feature.getName()), + "Empty component name found in " + file.getAbsolutePath()); + + List patterns = new ArrayList(); + patterns.add(fnPattern); + FnPatternList patternList = new FnPatternList(); + patternList.setInclude(patterns); + patternList.setFlags(MATCH_FLAGS); + FeaturePattern fp = new FeaturePattern(); + fp.setFeature(feature); + fp.setFilePatterns(patternList); + getPatterns().add(fp); + + FileInfoScanner.log.info("Found .info.json file at " + file.getAbsolutePath() + + ", with pattern " + fnPattern + ", and component " + feature); + } + } + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/FnMatch.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/FnMatch.java new file mode 100644 index 0000000..9a65859 --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/FnMatch.java @@ -0,0 +1,246 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ +/* $OpenBSD: fnmatch.c,v 1.13 2006/03/31 05:34:14 deraadt Exp $ */ + +package com.skcraft.launcher.builder; + +import java.util.EnumSet; + +/* + * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. + * Compares a filename or pathname to a pattern. + */ +public class FnMatch { + + public static enum Flag { + + /** Disable backslash escaping. */ + NOESCAPE, + /** Slash must be matched by slash. */ + PATHNAME, + /** Period must be matched by period. */ + PERIOD, + /** Ignore / after Imatch. */ + LEADING_DIR, + /** Case insensitive search. */ + CASEFOLD + } + private static final int RANGE_ERROR = -1; + private static final int RANGE_NOMATCH = 0; + + public static boolean fnmatch(String pattern, String string, EnumSet flags) { + return match(pattern, 0, string, 0, flags); + } + + public static boolean fnmatch(String pattern, String string, int stringPos, Flag flag) { + return match(pattern, 0, string, stringPos, EnumSet.of(flag)); + } + + public static boolean fnmatch(String pattern, String string, int stringPos) { + return match(pattern, 0, string, stringPos, EnumSet.noneOf(Flag.class)); + } + + public static boolean fnmatch(String pattern, String string) { + return fnmatch(pattern, string, 0); + } + + private static boolean match(String pattern, int patternPos, + String string, int stringPos, EnumSet flags) { + char c; + + while (true) { + if (patternPos >= pattern.length()) { + if (flags.contains(Flag.LEADING_DIR) && string.charAt(stringPos) == '/') { + return true; + } + return stringPos == string.length(); + } + c = pattern.charAt(patternPos++); + switch (c) { + case '?': + if (stringPos >= string.length()) { + return false; + } + if (string.charAt(stringPos) == '/' && flags.contains(Flag.PATHNAME)) { + return false; + } + if (hasLeadingPeriod(string, stringPos, flags)) { + return false; + } + ++stringPos; + continue; + case '*': + /* Collapse multiple stars. */ + while (patternPos < pattern.length() && + (c = pattern.charAt(patternPos)) == '*') { + patternPos++; + } + + if (hasLeadingPeriod(string, stringPos, flags)) { + return false; + } + + /* Optimize for pattern with * at end or before /. */ + if (patternPos == pattern.length()) { + if (flags.contains(Flag.PATHNAME)) { + return flags.contains(Flag.LEADING_DIR) || + string.indexOf('/', stringPos) == -1; + } + return true; + } else if (c == '/' && flags.contains(Flag.PATHNAME)) { + stringPos = string.indexOf('/', stringPos); + if (stringPos == -1) { + return false; + } + continue; + } + + /* General case, use recursion. */ + while (stringPos < string.length()) { + if (flags.contains(Flag.PERIOD)) { + flags = EnumSet.copyOf(flags); + flags.remove(Flag.PERIOD); + } + if (match(pattern, patternPos, string, stringPos, flags)) { + return true; + } + if (string.charAt(stringPos) == '/' && flags.contains(Flag.PATHNAME)) { + break; + } + ++stringPos; + } + return false; + + case '[': + if (stringPos >= string.length()) { + return false; + } + if (string.charAt(stringPos) == '/' && flags.contains(Flag.PATHNAME)) { + return false; + } + if (hasLeadingPeriod(string, stringPos, flags)) { + return false; + } + + int result = matchRange(pattern, patternPos, string.charAt(stringPos), flags); + if (result == RANGE_ERROR) /* not a good range, treat as normal text */ { + break; + } + + if (result == RANGE_NOMATCH) { + return false; + } + + patternPos = result; + ++stringPos; + continue; + + case '\\': + if (!flags.contains(Flag.NOESCAPE)) { + if (patternPos >= pattern.length()) { + c = '\\'; + } else { + c = pattern.charAt(patternPos++); + } + } + break; + } + + if (stringPos >= string.length()) { + return false; + } + if (c != string.charAt(stringPos) && + !(flags.contains(Flag.CASEFOLD) && + Character.toLowerCase(c) == Character.toLowerCase(string.charAt(stringPos)))) { + return false; + } + ++stringPos; + } + /* NOTREACHED */ + } + + private static boolean hasLeadingPeriod(String string, int stringPos, EnumSet flags) { + if (stringPos > string.length() - 1) + return false; + return (stringPos == 0 + || (flags.contains(Flag.PATHNAME) && string.charAt(stringPos - 1) == '/')) + && string.charAt(stringPos) == '.' && flags.contains(Flag.PERIOD); + } + + private static int matchRange(String pattern, int patternPos, char test, EnumSet flags) { + boolean negate, ok; + char c, c2; + + if (patternPos >= pattern.length()) { + return RANGE_ERROR; + } + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + c = pattern.charAt(patternPos); + negate = c == '!' || c == '^'; + if (negate) { + ++patternPos; + } + + if (flags.contains(Flag.CASEFOLD)) { + test = Character.toLowerCase(test); + } + + /* + * A right bracket shall lose its special meaning and represent + * itself in a bracket expression if it occurs first in the list. + * -- POSIX.2 2.8.3.2 + */ + ok = false; + while (true) { + if (patternPos >= pattern.length()) { + return RANGE_ERROR; + } + + c = pattern.charAt(patternPos++); + if (c == ']') { + break; + } + + if (c == '\\' && !flags.contains(Flag.NOESCAPE)) { + c = pattern.charAt(patternPos++); + } + if (c == '/' && flags.contains(Flag.PATHNAME)) { + return RANGE_NOMATCH; + } + if (flags.contains(Flag.CASEFOLD)) { + c = Character.toLowerCase(c); + } + if (pattern.charAt(patternPos) == '-' && + patternPos + 1 < pattern.length() && + (c2 = pattern.charAt(patternPos + 1)) != ']') { + patternPos += 2; + if (c2 == '\\' && !flags.contains(Flag.NOESCAPE)) { + if (patternPos >= pattern.length()) { + return RANGE_ERROR; + } + c = pattern.charAt(patternPos++); + } + if (flags.contains(Flag.CASEFOLD)) { + c2 = Character.toLowerCase(c2); + } + if (c <= test && test <= c2) { + ok = true; + } + } else if (c == test) { + ok = true; + } + } + + return ok == negate ? RANGE_NOMATCH : patternPos; + } +} \ No newline at end of file diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/FnPatternList.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/FnPatternList.java new file mode 100644 index 0000000..dd9c3b0 --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/FnPatternList.java @@ -0,0 +1,43 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; + +@Data +public class FnPatternList { + + private static final EnumSet DEFAULT_FLAGS = EnumSet.of( + FnMatch.Flag.CASEFOLD, FnMatch.Flag.PERIOD); + + private List include; + private List exclude; + @Getter @Setter @JsonIgnore + private EnumSet flags = DEFAULT_FLAGS; + + public boolean matches(String path) { + return include != null && matches(path, include) && (exclude == null || !matches(path, exclude)); + } + + public boolean matches(String path, Collection patterns) { + for (String pattern : patterns) { + if (FnMatch.fnmatch(pattern, path, flags)) { + return true; + } + } + + return false; + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/JarFileFilter.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/JarFileFilter.java new file mode 100644 index 0000000..31ca410 --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/JarFileFilter.java @@ -0,0 +1,19 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import java.io.File; +import java.io.FileFilter; + +public class JarFileFilter implements FileFilter { + + @Override + public boolean accept(File pathname) { + return pathname.getName().toLowerCase().endsWith(".jar"); + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java new file mode 100644 index 0000000..303c63d --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java @@ -0,0 +1,407 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterException; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.io.ByteStreams; +import com.google.common.io.CharStreams; +import com.google.common.io.Closer; +import com.google.common.io.Files; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.LauncherUtils; +import com.skcraft.launcher.model.loader.InstallProfile; +import com.skcraft.launcher.model.minecraft.Library; +import com.skcraft.launcher.model.minecraft.VersionManifest; +import com.skcraft.launcher.model.modpack.Manifest; +import com.skcraft.launcher.util.Environment; +import com.skcraft.launcher.util.HttpRequest; +import com.skcraft.launcher.util.SimpleLogFormatter; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.java.Log; + +import java.io.*; +import java.net.URL; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Properties; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.emptyToNull; +import static com.skcraft.launcher.util.HttpRequest.url; + +/** + * Builds packages for the launcher. + */ +@Log +public class PackageBuilder { + + private static final Pattern TWEAK_CLASS_ARG = Pattern.compile("--tweakClass\\s+([^\\s]+)"); + + private final Properties properties; + private final ObjectMapper mapper; + private ObjectWriter writer; + private final Manifest manifest; + private final PropertiesApplicator applicator; + @Getter + private boolean prettyPrint = false; + private List loaderLibraries = Lists.newArrayList(); + private List mavenRepos; + + /** + * Create a new package builder. + * + * @param mapper the mapper + * @param manifest the manifest + */ + public PackageBuilder(@NonNull ObjectMapper mapper, @NonNull Manifest manifest) throws IOException { + this.properties = LauncherUtils.loadProperties(Launcher.class, + "launcher.properties", "com.skcraft.launcher.propertiesFile"); + + this.mapper = mapper; + this.manifest = manifest; + this.applicator = new PropertiesApplicator(manifest); + setPrettyPrint(false); // Set writer + + Closer closer = Closer.create(); + try { + mavenRepos = mapper.readValue(closer.register(Launcher.class.getResourceAsStream("maven_repos.json")), new TypeReference>() { + }); + } finally { + closer.close(); + } + } + + public void setPrettyPrint(boolean prettyPrint) { + if (prettyPrint) { + writer = mapper.writerWithDefaultPrettyPrinter(); + } else { + writer = mapper.writer(); + } + this.prettyPrint = prettyPrint; + } + + public void scan(File dir) throws IOException { + logSection("Scanning for .info.json files..."); + + FileInfoScanner scanner = new FileInfoScanner(mapper); + scanner.walk(dir); + for (FeaturePattern pattern : scanner.getPatterns()) { + applicator.register(pattern); + } + } + + public void addFiles(File dir, File destDir) throws IOException { + logSection("Adding files to modpack..."); + + ClientFileCollector collector = new ClientFileCollector(this.manifest, applicator, destDir); + collector.walk(dir); + } + + public void addLoaders(File dir, File librariesDir) { + logSection("Checking for mod loaders to install..."); + + LinkedHashSet collected = new LinkedHashSet(); + + File[] files = dir.listFiles(new JarFileFilter()); + if (files != null) { + for (File file : files) { + try { + processLoader(collected, file, librariesDir); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to add the loader at " + file.getAbsolutePath(), e); + } + } + } + + this.loaderLibraries.addAll(collected); + + VersionManifest version = manifest.getVersionManifest(); + collected.addAll(version.getLibraries()); + version.setLibraries(collected); + } + + private void processLoader(LinkedHashSet loaderLibraries, File file, File librariesDir) throws IOException { + log.info("Installing " + file.getName() + "..."); + + JarFile jarFile = new JarFile(file); + Closer closer = Closer.create(); + + try { + ZipEntry profileEntry = BuilderUtils.getZipEntry(jarFile, "install_profile.json"); + + if (profileEntry != null) { + InputStream stream = jarFile.getInputStream(profileEntry); + + // Read file + String data = CharStreams.toString(closer.register(new InputStreamReader(stream))); + data = data.replaceAll(",\\s*\\}", "}"); // Fix issues with trailing commas + + InstallProfile profile = mapper.readValue(data, InstallProfile.class); + VersionManifest version = manifest.getVersionManifest(); + + // Copy tweak class arguments + String args = profile.getVersionInfo().getMinecraftArguments(); + if (args != null) { + String existingArgs = Strings.nullToEmpty(version.getMinecraftArguments()); + + Matcher m = TWEAK_CLASS_ARG.matcher(args); + while (m.find()) { + version.setMinecraftArguments(existingArgs + " " + m.group()); + log.info("Adding " + m.group() + " to launch arguments"); + } + } + + // Add libraries + List libraries = profile.getVersionInfo().getLibraries(); + if (libraries != null) { + for (Library library : libraries) { + if (!version.getLibraries().contains(library)) { + loaderLibraries.add(library); + } + } + } + + // Copy main class + String mainClass = profile.getVersionInfo().getMainClass(); + if (mainClass != null) { + version.setMainClass(mainClass); + log.info("Using " + mainClass + " as the main class"); + } + + // Extract the library + String filePath = profile.getInstallData().getFilePath(); + String libraryPath = profile.getInstallData().getPath(); + + if (filePath != null && libraryPath != null) { + ZipEntry libraryEntry = BuilderUtils.getZipEntry(jarFile, filePath); + + if (libraryEntry != null) { + Library library = new Library(); + library.setName(libraryPath); + File extractPath = new File(librariesDir, library.getPath(Environment.getInstance())); + Files.createParentDirs(extractPath); + ByteStreams.copy(closer.register(jarFile.getInputStream(libraryEntry)), Files.newOutputStreamSupplier(extractPath)); + } else { + log.warning("Could not find the file '" + filePath + "' in " + file.getAbsolutePath() + ", which means that this mod loader will not work correctly"); + } + } + } else { + log.warning("The file at " + file.getAbsolutePath() + " did not appear to have an " + + "install_profile.json file inside -- is it actually an installer for a mod loader?"); + } + } finally { + closer.close(); + jarFile.close(); + } + } + + public void downloadLibraries(File librariesDir) throws IOException, InterruptedException { + logSection("Downloading libraries..."); + + // TODO: Download libraries for different environments -- As of writing, this is not an issue + Environment env = Environment.getInstance(); + + for (Library library : loaderLibraries) { + File outputPath = new File(librariesDir, library.getPath(env)); + + if (!outputPath.exists()) { + Files.createParentDirs(outputPath); + boolean found = false; + + // Gather a list of repositories to download from + List sources = Lists.newArrayList(); + if (library.getBaseUrl() != null) { + sources.add(library.getBaseUrl()); + } + sources.addAll(mavenRepos); + + // Try each repository + for (String baseUrl : sources) { + String pathname = library.getPath(env); + + // Some repositories compress their files + List compressors = BuilderUtils.getCompressors(baseUrl); + for (Compressor compressor : Lists.reverse(compressors)) { + pathname = compressor.transformPathname(pathname); + } + + URL url = new URL(baseUrl + pathname); + File tempFile = File.createTempFile("launcherlib", null); + + try { + log.info("Downloading library " + library.getName() + " from " + url + "..."); + HttpRequest.get(url).execute().expectResponseCode(200).saveContent(tempFile); + } catch (IOException e) { + log.info("Could not get file from " + url + ": " + e.getMessage()); + continue; + } + + // Decompress (if needed) and write to file + Closer closer = Closer.create(); + InputStream inputStream = closer.register(new FileInputStream(tempFile)); + inputStream = closer.register(new BufferedInputStream(inputStream)); + for (Compressor compressor : compressors) { + inputStream = closer.register(compressor.createInputStream(inputStream)); + } + ByteStreams.copy(inputStream, closer.register(new FileOutputStream(outputPath))); + + tempFile.delete(); + + found = true; + break; + } + + if (!found) { + log.warning("!! Failed to download the library " + library.getName() + " -- this means your copy of the libraries will lack this file"); + } + } + } + } + + public void validateManifest() { + checkNotNull(emptyToNull(manifest.getName()), "Package name is not defined"); + checkNotNull(emptyToNull(manifest.getGameVersion()), "Game version is not defined"); + } + + public void readConfig(File path) throws IOException { + if (path != null) { + BuilderConfig config = read(path, BuilderConfig.class); + config.update(manifest); + config.registerProperties(applicator); + } + } + + public void readVersionManifest(File path) throws IOException, InterruptedException { + logSection("Reading version manifest..."); + + if (path.exists()) { + VersionManifest versionManifest = read(path, VersionManifest.class); + manifest.setVersionManifest(versionManifest); + + log.info("Loaded version manifest from " + path.getAbsolutePath()); + } else { + URL url = url(String.format( + properties.getProperty("versionManifestUrl"), + manifest.getGameVersion())); + + log.info("Fetching version manifest from " + url + "..."); + + manifest.setVersionManifest(HttpRequest + .get(url) + .execute() + .expectResponseCode(200) + .returnContent() + .asJson(VersionManifest.class)); + } + } + + public void writeManifest(@NonNull File path) throws IOException { + logSection("Writing manifest..."); + + manifest.setFeatures(applicator.getFeaturesInUse()); + VersionManifest versionManifest = manifest.getVersionManifest(); + if (versionManifest != null) { + versionManifest.setId(manifest.getGameVersion()); + } + validateManifest(); + path.getAbsoluteFile().getParentFile().mkdirs(); + writer.writeValue(path, manifest); + + log.info("Wrote manifest to " + path.getAbsolutePath()); + } + + private static BuilderOptions parseArgs(String[] args) { + BuilderOptions options = new BuilderOptions(); + new JCommander(options, args); + options.choosePaths(); + return options; + } + + private V read(File path, Class clazz) throws IOException { + try { + if (path == null) { + return clazz.newInstance(); + } else { + return mapper.readValue(path, clazz); + } + } catch (InstantiationException e) { + throw new IOException("Failed to create " + clazz.getCanonicalName(), e); + } catch (IllegalAccessException e) { + throw new IOException("Failed to create " + clazz.getCanonicalName(), e); + } + } + + /** + * Build a package given the arguments. + * + * @param args arguments + * @throws IOException thrown on I/O error + * @throws InterruptedException on interruption + */ + public static void main(String[] args) throws IOException, InterruptedException { + BuilderOptions options; + try { + options = parseArgs(args); + } catch (ParameterException e) { + new JCommander().usage(); + System.err.println("error: " + e.getMessage()); + System.exit(1); + return; + } + + // Initialize + SimpleLogFormatter.configureGlobalLogger(); + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); + + Manifest manifest = new Manifest(); + manifest.setMinimumVersion(Manifest.MIN_PROTOCOL_VERSION); + PackageBuilder builder = new PackageBuilder(mapper, manifest); + builder.setPrettyPrint(options.isPrettyPrinting()); + + // From config + builder.readConfig(options.getConfigPath()); + builder.readVersionManifest(options.getVersionManifestPath()); + + // From options + manifest.updateName(options.getName()); + manifest.updateTitle(options.getTitle()); + manifest.updateGameVersion(options.getGameVersion()); + manifest.setVersion(options.getVersion()); + manifest.setLibrariesLocation(options.getLibrariesLocation()); + manifest.setObjectsLocation(options.getObjectsLocation()); + + builder.scan(options.getFilesDir()); + builder.addFiles(options.getFilesDir(), options.getObjectsDir()); + builder.addLoaders(options.getLoadersDir(), options.getLibrariesDir()); + builder.downloadLibraries(options.getLibrariesDir()); + builder.writeManifest(options.getManifestPath()); + + logSection("Done"); + + log.info("Now upload the contents of " + options.getOutputPath() + " to your web server or CDN!"); + } + + private static void logSection(String name) { + log.info(""); + log.info("--- " + name + " ---"); + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/PropertiesApplicator.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/PropertiesApplicator.java new file mode 100644 index 0000000..586a04b --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/PropertiesApplicator.java @@ -0,0 +1,74 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.skcraft.launcher.model.modpack.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class PropertiesApplicator { + + private final Manifest manifest; + private final Set used = new HashSet(); + private final List features = new ArrayList(); + @Getter @Setter + private FnPatternList userFiles; + + public PropertiesApplicator(Manifest manifest) { + this.manifest = manifest; + } + + public void apply(ManifestEntry entry) { + if (entry instanceof FileInstall) { + apply((FileInstall) entry); + } + } + + private void apply(FileInstall entry) { + String path = entry.getTargetPath(); + entry.setWhen(fromFeature(path)); + entry.setUserFile(isUserFile(path)); + } + + public boolean isUserFile(String path) { + if (userFiles != null) { + return userFiles.matches(path); + } else { + return false; + } + } + + public Condition fromFeature(String path) { + List found = new ArrayList(); + for (FeaturePattern pattern : features) { + if (pattern.matches(path)) { + used.add(pattern.getFeature()); + found.add(pattern.getFeature()); + } + } + + if (!found.isEmpty()) { + return new RequireAny(found); + } else { + return null; + } + } + + public void register(FeaturePattern component) { + features.add(component); + } + + public List getFeaturesInUse() { + return new ArrayList(used); + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/ServerCopyExport.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/ServerCopyExport.java new file mode 100644 index 0000000..fa3da44 --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/ServerCopyExport.java @@ -0,0 +1,61 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.beust.jcommander.JCommander; +import com.google.common.io.Files; +import com.skcraft.launcher.util.SimpleLogFormatter; +import lombok.NonNull; +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; + +@Log +public class ServerCopyExport extends DirectoryWalker { + + private final File destDir; + + public ServerCopyExport(@NonNull File destDir) { + this.destDir = destDir; + } + + @Override + protected DirectoryBehavior getBehavior(String name) { + if (name.startsWith(".")) { + return DirectoryBehavior.SKIP; + } else if (name.equals("_SERVER")) { + return DirectoryBehavior.IGNORE; + } else if (name.equals("_CLIENT")) { + return DirectoryBehavior.SKIP; + } else { + return DirectoryBehavior.CONTINUE; + } + } + + @Override + protected void onFile(File file, String relPath) throws IOException { + File dest = new File(destDir, relPath); + + log.info("Copying " + file.getAbsolutePath() + " to " + dest.getAbsolutePath()); + dest.getParentFile().mkdirs(); + Files.copy(file, dest); + } + + public static void main(String[] args) throws IOException { + SimpleLogFormatter.configureGlobalLogger(); + + ServerExportOptions options = new ServerExportOptions(); + new JCommander(options, args); + + log.info("From: " + options.getSourceDir().getAbsolutePath()); + log.info("To: " + options.getDestDir().getAbsolutePath()); + ServerCopyExport task = new ServerCopyExport(options.getDestDir()); + task.walk(options.getSourceDir()); + } + +} diff --git a/launcher-builder/src/main/java/com/skcraft/launcher/builder/ServerExportOptions.java b/launcher-builder/src/main/java/com/skcraft/launcher/builder/ServerExportOptions.java new file mode 100644 index 0000000..2e243f4 --- /dev/null +++ b/launcher-builder/src/main/java/com/skcraft/launcher/builder/ServerExportOptions.java @@ -0,0 +1,22 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.builder; + +import com.beust.jcommander.Parameter; +import lombok.Data; + +import java.io.File; + +@Data +public class ServerExportOptions { + + @Parameter(names = "--source", required = true) + private File sourceDir; + @Parameter(names = "--dest", required = true) + private File destDir; + +} diff --git a/launcher/build.gradle b/launcher/build.gradle new file mode 100644 index 0000000..7385044 --- /dev/null +++ b/launcher/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.github.johnrengelman.shadow' + +jar { + manifest { + attributes("Main-Class": "com.skcraft.launcher.Launcher") + } +} + +dependencies { + compile 'org.projectlombok:lombok:1.12.2' + compile 'com.fasterxml.jackson.core:jackson-databind:2.3.0' + compile 'commons-lang:commons-lang:2.6' + compile 'commons-io:commons-io:1.2' + compile 'com.google.guava:guava:15.0' + compile 'com.beust:jcommander:1.32' + compile 'org.tukaani:xz:1.0' + compile 'org.apache.commons:commons-compress:1.9' +} + +processResources { + filesMatching('**/*.properties') { + filter { + it.replace('${project.version}', project.version) + } + } +} + +shadowJar { + dependencies { + exclude(dependency('org.projectlombok:lombok')) + } +} + +build.dependsOn(shadowJar) \ No newline at end of file diff --git a/launcher/src/main/java/com/skcraft/concurrency/DefaultProgress.java b/launcher/src/main/java/com/skcraft/concurrency/DefaultProgress.java new file mode 100644 index 0000000..fd8a681 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/concurrency/DefaultProgress.java @@ -0,0 +1,28 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.concurrency; + +import lombok.Data; + +/** + * A simple default implementation of {@link com.skcraft.concurrency.ProgressObservable} + * with settable properties. + */ +@Data +public class DefaultProgress implements ProgressObservable { + + private String status; + private double progress = -1; + + public DefaultProgress() { + } + + public DefaultProgress(double progress, String status) { + this.progress = progress; + this.status = status; + } +} diff --git a/launcher/src/main/java/com/skcraft/concurrency/ObservableFuture.java b/launcher/src/main/java/com/skcraft/concurrency/ObservableFuture.java new file mode 100644 index 0000000..1458d17 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/concurrency/ObservableFuture.java @@ -0,0 +1,83 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.concurrency; + +import com.google.common.util.concurrent.ListenableFuture; +import lombok.NonNull; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A pair of ProgressObservable and ListenableFuture. + * + * @param the result type + */ +public class ObservableFuture implements ListenableFuture, ProgressObservable { + + private final ListenableFuture future; + private final ProgressObservable observable; + + /** + * Construct a new ObservableFuture. + * + * @param future the delegate future + * @param observable the observable + */ + public ObservableFuture(@NonNull ListenableFuture future, @NonNull ProgressObservable observable) { + this.future = future; + this.observable = observable; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return future.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return future.isCancelled(); + } + + @Override + public void addListener(Runnable listener, Executor executor) { + future.addListener(listener, executor); + } + + @Override + public boolean isDone() { + return future.isDone(); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + return future.get(); + } + + @Override + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return future.get(timeout, unit); + } + + @Override + public String toString() { + return observable.toString(); + } + + @Override + public double getProgress() { + return observable.getProgress(); + } + + @Override + public String getStatus() { + return observable.getStatus(); + } + +} diff --git a/launcher/src/main/java/com/skcraft/concurrency/ProgressFilter.java b/launcher/src/main/java/com/skcraft/concurrency/ProgressFilter.java new file mode 100644 index 0000000..d9cf69e --- /dev/null +++ b/launcher/src/main/java/com/skcraft/concurrency/ProgressFilter.java @@ -0,0 +1,35 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.concurrency; + +public class ProgressFilter implements ProgressObservable { + + private final ProgressObservable delegate; + private final double offset; + private final double portion; + + public ProgressFilter(ProgressObservable delegate, double offset, double portion) { + this.delegate = delegate; + this.offset = offset; + this.portion = portion; + } + + @Override + public double getProgress() { + return offset + portion * Math.max(0, delegate.getProgress()); + } + + @Override + public String getStatus() { + return delegate.getStatus(); + } + + public static ProgressObservable between(ProgressObservable delegate, double from, double to) { + return new ProgressFilter(delegate, from, to - from); + } + +} diff --git a/launcher/src/main/java/com/skcraft/concurrency/ProgressObservable.java b/launcher/src/main/java/com/skcraft/concurrency/ProgressObservable.java new file mode 100644 index 0000000..2179e6a --- /dev/null +++ b/launcher/src/main/java/com/skcraft/concurrency/ProgressObservable.java @@ -0,0 +1,30 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.concurrency; + +/** + * Implementations of this interface can provide information on the progress + * of a task. + */ +public interface ProgressObservable { + + /** + * Get the progress value as a number between 0 and 1 (inclusive), or -1 + * if progress information is unavailable. + * + * @return the progress value + */ + double getProgress(); + + /** + * Get the current status text. + * + * @return the status text, or null if unavailable + */ + String getStatus(); + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/AssetsRoot.java b/launcher/src/main/java/com/skcraft/launcher/AssetsRoot.java new file mode 100644 index 0000000..11b94a1 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/AssetsRoot.java @@ -0,0 +1,144 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher; + +import com.google.common.io.Files; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.model.minecraft.Asset; +import com.skcraft.launcher.model.minecraft.AssetsIndex; +import com.skcraft.launcher.model.minecraft.VersionManifest; +import com.skcraft.launcher.persistence.Persistence; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.logging.Level; + +import static com.skcraft.launcher.util.SharedLocale._; + +/** + * Represents a directory that stores assets for Minecraft. The class has + * various methods that abstract operations involving the assets (such + * as getting the path to a certain object). + */ +@Log +public class AssetsRoot { + + @Getter + private final File dir; + + /** + * Create a new instance. + * + * @param dir the directory to the assets folder + */ + public AssetsRoot(@NonNull File dir) { + this.dir = dir; + } + + /** + * Get the path to the index .json file for a version manfiest. + * + * @param versionManifest the version manifest + * @return the file, which may not exist + */ + public File getIndexPath(VersionManifest versionManifest) { + return new File(dir, "indexes/" + versionManifest.getAssetsIndex() + ".json"); + } + + /** + * Get the local path for a given asset. + * + * @param asset the asset + * @return the file, which may not exist + */ + public File getObjectPath(Asset asset) { + String hash = asset.getHash(); + return new File(dir, "objects/" + hash.substring(0, 2) + "/" + hash); + } + + /** + * Create an instance of the assets tree builder, which copies the indexed + * assets (identified by hashes) into a directory where the assets + * have been renamed and moved to their real names and locations + * (i.e. sounds/whatever.ogg). + * + * @param versionManifest the version manifest + * @return the builder + * @throws LauncherException + */ + public AssetsTreeBuilder createAssetsBuilder(@NonNull VersionManifest versionManifest) throws LauncherException { + String indexId = versionManifest.getAssetsIndex(); + File path = getIndexPath(versionManifest); + AssetsIndex index = Persistence.read(path, AssetsIndex.class, true); + if (index == null || index.getObjects() == null) { + throw new LauncherException("Missing index at " + path, _("assets.missingIndex", path.getAbsolutePath())); + } + File treeDir = new File(dir, "virtual/" + indexId); + treeDir.mkdirs(); + return new AssetsTreeBuilder(index, treeDir); + } + + public class AssetsTreeBuilder implements ProgressObservable { + private final AssetsIndex index; + private final File destDir; + private final int count; + private int processed = 0; + + public AssetsTreeBuilder(AssetsIndex index, File destDir) { + this.index = index; + this.destDir = destDir; + count = index.getObjects().size(); + } + + public File build() throws IOException, LauncherException { + AssetsRoot.log.info("Building asset virtual tree at '" + destDir.getAbsolutePath() + "'..."); + + for (Map.Entry entry : index.getObjects().entrySet()) { + File objectPath = getObjectPath(entry.getValue()); + File virtualPath = new File(destDir, entry.getKey()); + virtualPath.getParentFile().mkdirs(); + if (!virtualPath.exists()) { + log.log(Level.INFO, "Copying {0} to {1}...", new Object[] { + objectPath.getAbsolutePath(), virtualPath.getAbsolutePath()}); + + if (!objectPath.exists()) { + String message = _("assets.missingObject", objectPath.getAbsolutePath()); + throw new LauncherException("Missing object " + objectPath.getAbsolutePath(), message); + } + + Files.copy(objectPath, virtualPath); + } + processed++; + } + + return destDir; + } + + @Override + public double getProgress() { + if (count == 0) { + return -1; + } else { + return processed / (double) count; + } + } + + @Override + public String getStatus() { + if (count == 0) { + return _("assets.expanding1", count, count - processed); + } else { + return _("assets.expandingN", count, count - processed); + } + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/Configuration.java b/launcher/src/main/java/com/skcraft/launcher/Configuration.java new file mode 100644 index 0000000..8471e68 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/Configuration.java @@ -0,0 +1,49 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * The configuration for the launcher. + *

+ * Default values are stored as field values. Note that if a default + * value is changed after the launcher has been deployed, it may not take effect + * for users who have already used the launcher because the old default + * values would have been written to disk. + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Configuration { + + private boolean offlineEnabled = false; + private String jvmPath; + private String jvmArgs; + private int minMemory = 1024; + private int maxMemory = 1024; + private int permGen = 128; + private int windowWidth = 854; + private int widowHeight = 480; + private boolean proxyEnabled = false; + private String proxyHost = "localhost"; + private int proxyPort = 8080; + private String proxyUsername; + private String proxyPassword; + private String gameKey; + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/Instance.java b/launcher/src/main/java/com/skcraft/launcher/Instance.java new file mode 100644 index 0000000..9fa4a02 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/Instance.java @@ -0,0 +1,152 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.skcraft.launcher.launch.JavaProcessBuilder; +import com.skcraft.launcher.model.modpack.LaunchModifier; +import lombok.Data; + +import java.io.File; +import java.net.URL; +import java.util.Date; + +/** + * An instance is a profile that represents one particular installation + * of the game, with separate files and so on. + */ +@Data +public class Instance implements Comparable { + + private String title; + private String name; + private String version; + private boolean updatePending; + private boolean installed; + private Date lastAccessed; + @JsonProperty("launch") + private LaunchModifier launchModifier; + + @JsonIgnore private File dir; + @JsonIgnore private URL manifestURL; + @JsonIgnore private int priority; + @JsonIgnore private boolean selected; + @JsonIgnore private boolean local; + + /** + * Get the tile of the instance, which might be the same as the + * instance name if no title is set. + * + * @return a title + */ + public String getTitle() { + return title != null ? title : name; + } + + /** + * Update the given process builder with launch settings that are + * specific to this instance. + * + * @param builder the process builder + */ + public void modify(JavaProcessBuilder builder) { + if (launchModifier != null) { + launchModifier.modify(builder); + } + } + + /** + * Get the file for the directory where Minecraft's game files are + * stored, including user files (screenshots, etc.). + * + * @return the content directory, which may not exist + */ + @JsonIgnore + public File getContentDir() { + File dir = new File(this.dir, "minecraft"); + if (!dir.exists()) { + dir.mkdirs(); + } + return dir; + } + + /** + * Get the file for the package manifest. + * + * @return the manifest path, which may not exist + */ + @JsonIgnore + public File getManifestPath() { + return new File(dir, "manifest.json"); + } + + /** + * Get the file for the Minecraft version manfiest file. + * + * @return the version path, which may not exist + */ + @JsonIgnore + public File getVersionPath() { + return new File(dir, "version.json"); + } + + /** + * Get the file for the custom JAR file. + * + * @return the JAR file, which may not exist + */ + @JsonIgnore + public File getCustomJarPath() { + return new File(getContentDir(), "custom_jar.jar"); + } + + @Override + public String toString() { + return name; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object other) { + return super.equals(other); + } + + @Override + public int compareTo(Instance o) { + if (isLocal() && !o.isLocal()) { + return -1; + } else if (!isLocal() && o.isLocal()) { + return 1; + } else if (isLocal() && o.isLocal()) { + Date otherDate = o.getLastAccessed(); + + if (otherDate == null && lastAccessed == null) { + return 0; + } else if (otherDate == null) { + return -1; + } else if (lastAccessed == null) { + return 1; + } else { + return -lastAccessed.compareTo(otherDate); + } + } else { + if (priority > o.priority) { + return -1; + } else if (priority < o.priority) { + return 1; + } else { + return 0; + } + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/InstanceList.java b/launcher/src/main/java/com/skcraft/launcher/InstanceList.java new file mode 100644 index 0000000..69d53df --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/InstanceList.java @@ -0,0 +1,215 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher; + +import com.skcraft.concurrency.DefaultProgress; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.model.modpack.ManifestInfo; +import com.skcraft.launcher.model.modpack.PackageList; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.util.HttpRequest; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.java.Log; +import org.apache.commons.io.filefilter.DirectoryFileFilter; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import static com.skcraft.launcher.LauncherUtils.concat; +import static com.skcraft.launcher.util.SharedLocale._; + +/** + * Stores the list of instances. + */ +@Log +public class InstanceList { + + private final Launcher launcher; + @Getter private final List instances = new ArrayList(); + + /** + * Create a new instance list. + * + * @param launcher the launcher + */ + public InstanceList(@NonNull Launcher launcher) { + this.launcher = launcher; + } + + /** + * Get the instance at a particular index. + * + * @param index the index + * @return the instance + */ + public synchronized Instance get(int index) { + return instances.get(index); + } + + /** + * Get the number of instances. + * + * @return the number of instances + */ + public synchronized int size() { + return instances.size(); + } + + /** + * Create a worker that loads the list of instances from disk and from + * the remote list of packages. + * + * @return the worker + */ + public Enumerator createEnumerator() { + return new Enumerator(); + } + + /** + * Get a list of selected instances. + * + * @return a list of instances + */ + public synchronized List getSelected() { + List selected = new ArrayList(); + for (Instance instance : instances) { + if (instance.isSelected()) { + selected.add(instance); + } + } + + return selected; + } + + /** + * Sort the list of instances. + */ + public synchronized void sort() { + Collections.sort(instances); + } + + public final class Enumerator implements Callable, ProgressObservable { + private ProgressObservable progress = new DefaultProgress(-1, null); + + private Enumerator() { + } + + @Override + public InstanceList call() throws Exception { + log.info("Enumerating instance list..."); + progress = new DefaultProgress(0, _("instanceLoader.loadingLocal")); + + List local = new ArrayList(); + List remote = new ArrayList(); + + File[] dirs = launcher.getInstancesDir().listFiles((FileFilter) DirectoryFileFilter.INSTANCE); + if (dirs != null) { + for (File dir : dirs) { + File file = new File(dir, "instance.json"); + Instance instance = Persistence.load(file, Instance.class); + instance.setDir(dir); + instance.setName(dir.getName()); + instance.setSelected(true); + instance.setLocal(true); + local.add(instance); + + log.info(instance.getName() + " local instance found at " + dir.getAbsolutePath()); + } + } + + progress = new DefaultProgress(0.3, _("instanceLoader.checkingRemote")); + + try { + URL packagesURL = launcher.getPackagesURL(); + + PackageList packages = HttpRequest + .get(packagesURL) + .execute() + .expectResponseCode(200) + .returnContent() + .asJson(PackageList.class); + + if (packages.getMinimumVersion() > Launcher.PROTOCOL_VERSION) { + throw new LauncherException("Update required", _("errors.updateRequiredError")); + } + + for (ManifestInfo manifest : packages.getPackages()) { + boolean foundLocal = false; + + for (Instance instance : local) { + if (instance.getName().equalsIgnoreCase(manifest.getName())) { + foundLocal = true; + + instance.setTitle(manifest.getTitle()); + instance.setPriority(manifest.getPriority()); + URL url = concat(packagesURL, manifest.getLocation()); + instance.setManifestURL(url); + + log.info("(" + instance.getName() + ").setManifestURL(" + url + ")"); + + // Check if an update is required + if (instance.getVersion() == null || !instance.getVersion().equals(manifest.getVersion())) { + instance.setUpdatePending(true); + instance.setVersion(manifest.getVersion()); + Persistence.commitAndForget(instance); + log.info(instance.getName() + " requires an update to " + manifest.getVersion()); + } + } + } + + if (!foundLocal) { + File dir = new File(launcher.getInstancesDir(), manifest.getName()); + File file = new File(dir, "instance.json"); + Instance instance = Persistence.load(file, Instance.class); + instance.setDir(dir); + instance.setTitle(manifest.getTitle()); + instance.setName(manifest.getName()); + instance.setVersion(manifest.getVersion()); + instance.setPriority(manifest.getPriority()); + instance.setSelected(false); + instance.setManifestURL(concat(packagesURL, manifest.getLocation())); + instance.setUpdatePending(true); + instance.setLocal(false); + remote.add(instance); + + log.info("Available remote instance: '" + instance.getName() + + "' at version " + instance.getVersion()); + } + } + } catch (IOException e) { + throw new IOException("The list of modpacks could not be downloaded.", e); + } finally { + synchronized (InstanceList.this) { + instances.clear(); + instances.addAll(local); + instances.addAll(remote); + + log.info(instances.size() + " instance(s) enumerated."); + } + } + + return InstanceList.this; + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return progress.getStatus(); + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/Launcher.java b/launcher/src/main/java/com/skcraft/launcher/Launcher.java new file mode 100644 index 0000000..3a3750e --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/Launcher.java @@ -0,0 +1,366 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterException; +import com.google.common.base.Strings; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.skcraft.launcher.auth.AccountList; +import com.skcraft.launcher.auth.LoginService; +import com.skcraft.launcher.auth.YggdrasilLoginService; +import com.skcraft.launcher.dialog.LauncherFrame; +import com.skcraft.launcher.model.minecraft.VersionManifest; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.util.HttpRequest; +import com.skcraft.launcher.util.SharedLocale; +import com.skcraft.launcher.util.SimpleLogFormatter; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.java.Log; +import org.apache.commons.io.FileUtils; + +import javax.swing.*; +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Locale; +import java.util.Properties; +import java.util.concurrent.Executors; +import java.util.logging.Level; + +/** + * The main entry point for the launcher. + */ +@Log +public final class Launcher { + + public static final int PROTOCOL_VERSION = 2; + + @Getter + private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); + @Getter private final File baseDir; + @Getter private final Properties properties; + @Getter private final InstanceList instances; + @Getter private final Configuration config; + @Getter private final AccountList accounts; + @Getter private final AssetsRoot assets; + + /** + * Create a new launcher instance with the given base directory. + * + * @param baseDir the base directory + * @throws java.io.IOException on load error + */ + public Launcher(@NonNull File baseDir) throws IOException { + SharedLocale.loadBundle("com.skcraft.launcher.lang.Launcher", Locale.getDefault()); + + this.baseDir = baseDir; + this.properties = LauncherUtils.loadProperties(Launcher.class, + "launcher.properties", "com.skcraft.launcher.propertiesFile"); + this.instances = new InstanceList(this); + this.assets = new AssetsRoot(new File(baseDir, "assets")); + this.config = Persistence.load(new File(baseDir, "config.json"), Configuration.class); + this.accounts = Persistence.load(new File(baseDir, "accounts.dat"), AccountList.class); + + if (accounts.getSize() > 0) { + accounts.setSelectedItem(accounts.getElementAt(0)); + } + + executor.submit(new Runnable() { + @Override + public void run() { + cleanupExtractDir(); + } + }); + } + + /** + * Get the launcher version. + * + * @return the launcher version + */ + public String getVersion() { + String version = getProperties().getProperty("version"); + if (version.equals("${project.version}")) { + return "1.0.0-SNAPSHOT"; + } + return version; + } + + /** + * Get a login service. + * + * @return a login service + */ + public LoginService getLoginService() { + return new YggdrasilLoginService(HttpRequest.url(getProperties().getProperty("yggdrasilAuthUrl"))); + } + + /** + * Get the directory containing the instances. + * + * @return the instances dir + */ + public File getInstancesDir() { + return new File(getBaseDir(), "instances"); + } + + /** + * Get the directory to store temporary files. + * + * @return the temporary directory + */ + public File getTemporaryDir() { + return new File(getBaseDir(), "temp"); + } + + /** + * Get the directory to store temporary install files. + * + * @return the temporary install directory + */ + public File getInstallerDir() { + return new File(getTemporaryDir(), "install"); + } + + /** + * Get the directory to store temporarily extracted files. + * + * @return the directory + */ + private File getExtractDir() { + return new File(getTemporaryDir(), "extract"); + } + + /** + * Delete old extracted files. + */ + public void cleanupExtractDir() { + log.info("Cleaning up temporary extracted files directory..."); + + final long now = System.currentTimeMillis(); + + File[] dirs = getExtractDir().listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + try { + long time = Long.parseLong(pathname.getName()); + return (now - time) > (1000 * 60 * 60); + } catch (NumberFormatException e) { + return false; + } + } + }); + + if (dirs != null) { + for (File dir : dirs) { + log.info("Removing " + dir.getAbsolutePath() + "..."); + try { + FileUtils.deleteDirectory(dir); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to delete " + dir.getAbsolutePath(), e); + } + } + } + } + + /** + * Create a new temporary directory to extract files to. + * + * @return the directory path + */ + public File createExtractDir() { + File dir = new File(getExtractDir(), String.valueOf(System.currentTimeMillis())); + dir.mkdirs(); + log.info("Created temporary directory " + dir.getAbsolutePath()); + return dir; + } + + /** + * Get the directory to store the launcher binaries. + * + * @return the libraries directory + */ + public File getLauncherBinariesDir() { + return new File(getBaseDir(), "launcher"); + } + + /** + * Get the directory to store common data files. + * + * @return the common data directory + */ + public File getCommonDataDir() { + return getBaseDir(); + } + + /** + * Get the directory to store libraries. + * + * @return the libraries directory + */ + public File getLibrariesDir() { + return new File(getCommonDataDir(), "libraries"); + } + + /** + * Get the directory to store versions. + * + * @return the versions directory + */ + public File getVersionsDir() { + return new File(getCommonDataDir(), "versions"); + } + + /** + * Get the directory to store a version. + * + * @param version the version + * @return the directory + */ + public File getVersionDir(String version) { + return new File(getVersionsDir(), version); + } + + /** + * Get the path to the JAR for the given version manifest. + * + * @param versionManifest the version manifest + * @return the path + */ + public File getJarPath(VersionManifest versionManifest) { + return new File(getVersionDir(versionManifest.getId()), versionManifest.getId() + ".jar"); + } + + /** + * Get the news URL. + * + * @return the news URL + */ + public URL getNewsURL() { + try { + return HttpRequest.url( + String.format(getProperties().getProperty("newsUrl"), + URLEncoder.encode(getVersion(), "UTF-8"))); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + /** + * Get the packages URL. + * + * @return the packages URL + */ + public URL getPackagesURL() { + try { + String key = Strings.nullToEmpty(getConfig().getGameKey()); + return HttpRequest.url( + String.format(getProperties().getProperty("packageListUrl"), + URLEncoder.encode(key, "UTF-8"))); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + /** + * Convenient method to fetch a property. + * + * @param key the key + * @return the property + */ + public String prop(String key) { + return getProperties().getProperty(key); + } + + /** + * Convenient method to fetch a property. + * + * @param key the key + * @param args formatting arguments + * @return the property + */ + public String prop(String key, String... args) { + return String.format(getProperties().getProperty(key), (Object[]) args); + } + + /** + * Convenient method to fetch a property. + * + * @param key the key + * @return the property + */ + public URL propUrl(String key) { + return HttpRequest.url(prop(key)); + } + + /** + * Convenient method to fetch a property. + * + * @param key the key + * @param args formatting arguments + * @return the property + */ + public URL propUrl(String key, String... args) { + return HttpRequest.url(prop(key, args)); + } + + /** + * Bootstrap. + * + * @param args args + */ + public static void main(String[] args) { + SimpleLogFormatter.configureGlobalLogger(); + + LauncherArguments options = new LauncherArguments(); + try { + new JCommander(options, args); + } catch (ParameterException e) { + System.err.print(e.getMessage()); + System.exit(1); + return; + } + + Integer bsVersion = options.getBootstrapVersion(); + log.info(bsVersion != null ? "Bootstrap version " + bsVersion + " detected" : "Not bootstrapped"); + + File dir = options.getDir(); + if (dir != null) { + log.info("Using given base directory " + dir.getAbsolutePath()); + } else { + dir = new File("."); + log.info("Using current directory " + dir.getAbsolutePath()); + } + + final File baseDir = dir; + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + UIManager.getDefaults().put("SplitPane.border", BorderFactory.createEmptyBorder()); + Launcher launcher = new Launcher(baseDir); + new LauncherFrame(launcher).setVisible(true); + } catch (Throwable t) { + log.log(Level.WARNING, "Load failure", t); + SwingHelper.showErrorDialog(null, "Uh oh! The updater couldn't be opened because a " + + "problem was encountered.", "Launcher error", t); + } + } + }); + + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/LauncherArguments.java b/launcher/src/main/java/com/skcraft/launcher/LauncherArguments.java new file mode 100644 index 0000000..2b340e0 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/LauncherArguments.java @@ -0,0 +1,29 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher; + +import com.beust.jcommander.Parameter; +import lombok.Data; + +import java.io.File; + +/** + * The command line arguments that the launcher accepts. + */ +@Data +public class LauncherArguments { + + @Parameter(names = "--dir") + private File dir; + + @Parameter(names = "--bootstrap-version") + private Integer bootstrapVersion; + + @Parameter(names = "--portable") + private boolean portable; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/LauncherException.java b/launcher/src/main/java/com/skcraft/launcher/LauncherException.java new file mode 100644 index 0000000..dea9d41 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/LauncherException.java @@ -0,0 +1,30 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher; + +/** + * A human-readable error wrapper. + */ +public class LauncherException extends Exception { + + private final String localizedMessage; + + public LauncherException(String message, String localizedMessage) { + super(message); + this.localizedMessage = localizedMessage; + } + + public LauncherException(Throwable cause, String localizedMessage) { + super(cause.getMessage(), cause); + this.localizedMessage = localizedMessage; + } + + @Override + public String getLocalizedMessage() { + return localizedMessage; + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/LauncherUtils.java b/launcher/src/main/java/com/skcraft/launcher/LauncherUtils.java new file mode 100644 index 0000000..0cf6e99 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/LauncherUtils.java @@ -0,0 +1,114 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher; + +import com.google.common.io.Closer; +import lombok.extern.java.Log; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Properties; +import java.util.regex.Pattern; + +@Log +public final class LauncherUtils { + + private static final Pattern absoluteUrlPattern = Pattern.compile("^[A-Za-z0-9\\-]+://.*$"); + + private LauncherUtils() { + } + + public static String getStackTrace(Throwable t) { + Writer result = new StringWriter(); + PrintWriter printWriter = new PrintWriter(result); + t.printStackTrace(printWriter); + return result.toString(); + } + + public static void checkInterrupted() throws InterruptedException { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + } + + public static Properties loadProperties(Class clazz, String name, String extraProperty) throws IOException { + Closer closer = Closer.create(); + Properties prop = new Properties(); + try { + InputStream in = closer.register(clazz.getResourceAsStream(name)); + prop.load(in); + String extraPath = System.getProperty(extraProperty); + if (extraPath != null) { + log.info("Loading extra properties for " + + clazz.getCanonicalName() + ":" + name + " from " + extraPath + "..."); + in = closer.register(new BufferedInputStream(closer.register(new FileInputStream(extraPath)))); + prop.load(in); + } + } finally { + closer.close(); + } + + return prop; + } + + public static URL concat(URL baseUrl, String url) throws MalformedURLException { + if (absoluteUrlPattern.matcher(url).matches()) { + return new URL(url); + } + + int lastSlash = baseUrl.toExternalForm().lastIndexOf("/"); + if (lastSlash == -1) { + return new URL(url); + } + + int firstSlash = url.indexOf("/"); + if (firstSlash == 0) { + boolean portSet = (baseUrl.getDefaultPort() == baseUrl.getPort() || + baseUrl.getPort() == -1); + String port = portSet ? "" : ":" + baseUrl.getPort(); + return new URL(baseUrl.getProtocol() + "://" + baseUrl.getHost() + + port + url); + } else { + return new URL(baseUrl.toExternalForm().substring(0, lastSlash + 1) + url); + } + } + + + + public static void interruptibleDelete(File file, List failures) throws IOException, InterruptedException { + checkInterrupted(); + + if (file.isDirectory()) { + File[] files = file.listFiles(); + + if (files == null) { + throw new IOException("Failed to list contents of " + file.getAbsolutePath()); + } + + for (File f : files) { + interruptibleDelete(f, failures); + } + + if (!file.delete()) { + log.warning("Failed to delete " + file.getAbsolutePath()); + failures.add(file); + } + } else { + if (!file.exists()) { + throw new FileNotFoundException("Does not exist: " + file); + } + + if (!file.delete()) { + log.warning("Failed to delete " + file.getAbsolutePath()); + failures.add(file); + } + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/Account.java b/launcher/src/main/java/com/skcraft/launcher/auth/Account.java new file mode 100644 index 0000000..e99bade --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/auth/Account.java @@ -0,0 +1,91 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.auth; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.google.common.base.Strings; +import lombok.Data; +import lombok.NonNull; + +import java.util.Date; + +/** + * A user account that can be stored and loaded. + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Account implements Comparable { + + private String id; + private String password; + private Date lastUsed; + + /** + * Create a new account. + */ + public Account() { + } + + /** + * Create a new account with the given ID. + * + * @param id the ID + */ + public Account(String id) { + setId(id); + } + + /** + * Set the account's stored password, that may be stored to disk. + * + * @param password the password + */ + public void setPassword(String password) { + if (password != null && password.isEmpty()) { + password = null; + } + this.password = Strings.emptyToNull(password); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Account account = (Account) o; + + if (!id.equalsIgnoreCase(account.id)) return false; + + return true; + } + + @Override + public int hashCode() { + return id.toLowerCase().hashCode(); + } + + @Override + public int compareTo(@NonNull Account o) { + Date otherDate = o.getLastUsed(); + + if (otherDate == null && lastUsed == null) { + return 0; + } else if (otherDate == null) { + return -1; + } else if (lastUsed == null) { + return 1; + } else { + return -lastUsed.compareTo(otherDate); + } + } + + @Override + public String toString() { + return getId(); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/AccountList.java b/launcher/src/main/java/com/skcraft/launcher/auth/AccountList.java new file mode 100644 index 0000000..ddf9cb0 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/auth/AccountList.java @@ -0,0 +1,134 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.auth; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.skcraft.launcher.persistence.Scrambled; +import lombok.Getter; +import lombok.NonNull; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * A list of accounts that can be stored to disk. + */ +@Scrambled("ACCOUNT_LIST") +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonAutoDetect( + getterVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE, + fieldVisibility = JsonAutoDetect.Visibility.NONE) +public class AccountList extends AbstractListModel implements ComboBoxModel { + + @JsonProperty + @Getter + private List accounts = new ArrayList(); + private transient Account selected; + + /** + * Add a new account. + * + *

If there is already an existing account with the same ID, then the + * new account will not be added.

+ * + * @param account the account to add + */ + public synchronized void add(@NonNull Account account) { + if (!accounts.contains(account)) { + accounts.add(account); + Collections.sort(accounts); + fireContentsChanged(this, 0, accounts.size()); + } + } + + /** + * Remove an account. + * + * @param account the account + */ + public synchronized void remove(@NonNull Account account) { + Iterator it = accounts.iterator(); + while (it.hasNext()) { + Account other = it.next(); + if (other.equals(account)) { + it.remove(); + fireContentsChanged(this, 0, accounts.size() + 1); + break; + } + } + } + + /** + * Set the list of accounts. + * + * @param accounts the list of accounts + */ + public synchronized void setAccounts(@NonNull List accounts) { + this.accounts = accounts; + Collections.sort(accounts); + } + + @Override + @JsonIgnore + public synchronized int getSize() { + return accounts.size(); + } + + @Override + public synchronized Account getElementAt(int index) { + try { + return accounts.get(index); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + @Override + public void setSelectedItem(Object item) { + if (item == null) { + selected = null; + return; + } + + if (item instanceof Account) { + this.selected = (Account) item; + } else { + String id = String.valueOf(item).trim(); + Account account = new Account(id); + for (Account test : accounts) { + if (test.equals(account)) { + account = test; + break; + } + } + selected = account; + } + + if (selected.getId() == null || selected.getId().isEmpty()) { + selected = null; + } + } + + @Override + @JsonIgnore + public Account getSelectedItem() { + return selected; + } + + public synchronized void forgetPasswords() { + for (Account account : accounts) { + account.setPassword(null); + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/AuthenticationException.java b/launcher/src/main/java/com/skcraft/launcher/auth/AuthenticationException.java new file mode 100644 index 0000000..4dc3f4f --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/auth/AuthenticationException.java @@ -0,0 +1,23 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.auth; + +import com.skcraft.launcher.LauncherException; + +/** + * Thrown on authentication error. + */ +public class AuthenticationException extends LauncherException { + + public AuthenticationException(String message, String localizedMessage) { + super(message, localizedMessage); + } + + public AuthenticationException(Throwable cause, String localizedMessage) { + super(cause, localizedMessage); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/LoginService.java b/launcher/src/main/java/com/skcraft/launcher/auth/LoginService.java new file mode 100644 index 0000000..60419ed --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/auth/LoginService.java @@ -0,0 +1,31 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.auth; + +import java.io.IOException; +import java.util.List; + +/** + * A service for creating authenticated sessions. + */ +public interface LoginService { + + /** + * Attempt to login with the given details. + * + * @param agent the game to authenticate for, such as "Minecraft" + * @param id the login ID + * @param password the password + * @return a list of authenticated sessions, which corresponds to identities + * @throws IOException thrown on I/O error + * @throws InterruptedException thrown if interrupted + * @throws AuthenticationException thrown on an authentication error + */ + List login(String agent, String id, String password) + throws IOException, InterruptedException, AuthenticationException; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/OfflineSession.java b/launcher/src/main/java/com/skcraft/launcher/auth/OfflineSession.java new file mode 100644 index 0000000..0431b2c --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/auth/OfflineSession.java @@ -0,0 +1,70 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.auth; + +import lombok.Getter; +import lombok.NonNull; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +/** + * An offline session. + */ +public class OfflineSession implements Session { + + private static Map dummyProperties = Collections.emptyMap(); + + @Getter + private final String name; + + /** + * Create a new offline session using the given player name. + * + * @param name the player name + */ + public OfflineSession(@NonNull String name) { + this.name = name; + } + + @Override + public String getUuid() { + return (new UUID(0, 0)).toString(); + } + + @Override + public String getClientToken() { + return "0"; + } + + @Override + public String getAccessToken() { + return "0"; + } + + @Override + public Map getUserProperties() { + return dummyProperties; + } + + @Override + public String getSessionToken() { + return "-"; + } + + @Override + public UserType getUserType() { + return UserType.LEGACY; + } + + @Override + public boolean isOnline() { + return false; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/Session.java b/launcher/src/main/java/com/skcraft/launcher/auth/Session.java new file mode 100644 index 0000000..95b3e58 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/auth/Session.java @@ -0,0 +1,74 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.auth; + +import java.util.Map; + +/** + * Represents an authenticated (or virtual) session. + */ +public interface Session { + + /** + * Get the user's UUID. + * + * @return the user's UUID + */ + String getUuid(); + + /** + * Get the user's game username. + * + * @return the username + */ + String getName(); + + /** + * Get the client token. + * + * @return client token + */ + String getClientToken(); + + /** + * Get the access token. + * + * @return the access token + */ + String getAccessToken(); + + /** + * Get a map of user properties. + * + * @return the map of user properties + */ + Map getUserProperties(); + + /** + * Get the session token string, which is in the form of + * token:accessToken:uuid for authenticated players, and + * simply - for offline players. + * + * @return the session token + */ + String getSessionToken(); + + /** + * Get the user type. + * + * @return the user type + */ + UserType getUserType(); + + /** + * Return true if the user is in an online session. + * + * @return true if online + */ + boolean isOnline(); + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/UserType.java b/launcher/src/main/java/com/skcraft/launcher/auth/UserType.java new file mode 100644 index 0000000..7dfeff6 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/auth/UserType.java @@ -0,0 +1,33 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.auth; + +/** + * Represents the type of user for the account. + */ +public enum UserType { + + /** + * Legacy accounts login with an account username. + */ + LEGACY, + /** + * Mojang accounts login with an email address. + */ + MOJANG; + + /** + * Return a lowercase version of the enum type. + * + * @return the lowercase name + */ + public String getName() { + return name().toLowerCase(); + } + + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/YggdrasilLoginService.java b/launcher/src/main/java/com/skcraft/launcher/auth/YggdrasilLoginService.java new file mode 100644 index 0000000..b686953 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/auth/YggdrasilLoginService.java @@ -0,0 +1,128 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.auth; + +import com.fasterxml.jackson.annotation.*; +import com.skcraft.launcher.util.HttpRequest; +import lombok.Data; +import lombok.NonNull; +import lombok.ToString; + +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Creates authenticated sessions using the Mojang Yggdrasil login protocol. + */ +public class YggdrasilLoginService implements LoginService { + + private final URL authUrl; + + /** + * Create a new login service with the given authentication URL. + * + * @param authUrl the authentication URL + */ + public YggdrasilLoginService(@NonNull URL authUrl) { + this.authUrl = authUrl; + } + + @Override + public List login(String agent, String id, String password) + throws IOException, InterruptedException, AuthenticationException { + Object payload = new AuthenticatePayload(new Agent(agent), id, password); + + HttpRequest request = HttpRequest + .post(authUrl) + .bodyJson(payload) + .execute(); + + if (request.getResponseCode() != 200) { + ErrorResponse error = request.returnContent().asJson(ErrorResponse.class); + throw new AuthenticationException(error.getErrorMessage(), error.getErrorMessage()); + } else { + AuthenticateResponse response = request.returnContent().asJson(AuthenticateResponse.class); + return response.getAvailableProfiles(); + } + } + + @Data + private static class Agent { + private final String name; + private final int version = 1; + } + + @Data + private static class AuthenticatePayload { + private final Agent agent; + private final String username; + private final String password; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + private static class AuthenticateResponse { + private String accessToken; + private String clientToken; + @JsonManagedReference private List availableProfiles; + private Profile selectedProfile; + } + + @Data + private static class ErrorResponse { + private String error; + private String errorMessage; + private String cause; + } + + /** + * Return in the list of available profiles. + */ + @Data + @ToString(exclude = "response") + @JsonIgnoreProperties(ignoreUnknown = true) + private static class Profile implements Session { + @JsonProperty("id") private String uuid; + private String name; + private boolean legacy; + @JsonIgnore private final Map userProperties = Collections.emptyMap(); + @JsonBackReference private AuthenticateResponse response; + + @Override + @JsonIgnore + public String getSessionToken() { + return String.format("token:%s:%s", getAccessToken(), getUuid()); + } + + @Override + @JsonIgnore + public String getClientToken() { + return response.getClientToken(); + } + + @Override + @JsonIgnore + public String getAccessToken() { + return response.getAccessToken(); + } + + @Override + @JsonIgnore + public UserType getUserType() { + return legacy ? UserType.LEGACY : UserType.MOJANG; + } + + @Override + public boolean isOnline() { + return true; + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/ConfigurationDialog.java b/launcher/src/main/java/com/skcraft/launcher/dialog/ConfigurationDialog.java new file mode 100644 index 0000000..a08b05e --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/ConfigurationDialog.java @@ -0,0 +1,155 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.dialog; + +import com.skcraft.launcher.Configuration; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.swing.*; +import com.skcraft.launcher.persistence.Persistence; +import lombok.NonNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import static com.skcraft.launcher.util.SharedLocale._; + +/** + * A dialog to modify configuration options. + */ +public class ConfigurationDialog extends JDialog { + + private final Configuration config; + private final ObjectSwingMapper mapper; + + private final JPanel tabContainer = new JPanel(new BorderLayout()); + private final JTabbedPane tabbedPane = new JTabbedPane(); + private final FormPanel javaSettingsPanel = new FormPanel(); + private final JTextField jvmPathText = new JTextField(); + private final JTextField jvmArgsText = new JTextField(); + private final JSpinner minMemorySpinner = new JSpinner(); + private final JSpinner maxMemorySpinner = new JSpinner(); + private final JSpinner permGenSpinner = new JSpinner(); + private final FormPanel gameSettingsPanel = new FormPanel(); + private final JSpinner widthSpinner = new JSpinner(); + private final JSpinner heightSpinner = new JSpinner(); + private final FormPanel proxySettingsPanel = new FormPanel(); + private final JCheckBox useProxyCheck = new JCheckBox(_("options.useProxyCheck")); + private final JTextField proxyHostText = new JTextField(); + private final JSpinner proxyPortText = new JSpinner(); + private final JTextField proxyUsernameText = new JTextField(); + private final JPasswordField proxyPasswordText = new JPasswordField(); + private final FormPanel advancedPanel = new FormPanel(); + private final JTextField gameKeyText = new JTextField(); + private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); + private final JButton okButton = new JButton(_("button.ok")); + private final JButton cancelButton = new JButton(_("button.cancel")); + private final JButton logButton = new JButton(_("options.launcherConsole")); + + /** + * Create a new configuration dialog. + * + * @param owner the window owner + * @param launcher the launcher + */ + public ConfigurationDialog(Window owner, @NonNull Launcher launcher) { + super(owner, ModalityType.DOCUMENT_MODAL); + + this.config = launcher.getConfig(); + mapper = new ObjectSwingMapper(config); + + setTitle(_("options.title")); + initComponents(); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setSize(new Dimension(400, 500)); + setResizable(false); + setLocationRelativeTo(owner); + + mapper.map(jvmPathText, "jvmPath"); + mapper.map(jvmArgsText, "jvmArgs"); + mapper.map(minMemorySpinner, "minMemory"); + mapper.map(maxMemorySpinner, "maxMemory"); + mapper.map(permGenSpinner, "permGen"); + mapper.map(widthSpinner, "windowWidth"); + mapper.map(heightSpinner, "widowHeight"); + mapper.map(useProxyCheck, "proxyEnabled"); + mapper.map(proxyHostText, "proxyHost"); + mapper.map(proxyPortText, "proxyPort"); + mapper.map(proxyUsernameText, "proxyUsername"); + mapper.map(proxyPasswordText, "proxyPassword"); + mapper.map(gameKeyText, "gameKey"); + + mapper.copyFromObject(); + } + + private void initComponents() { + javaSettingsPanel.addRow(new JLabel(_("options.jvmPath")), jvmPathText); + javaSettingsPanel.addRow(new JLabel(_("options.jvmArguments")), jvmArgsText); + javaSettingsPanel.addRow(Box.createVerticalStrut(15)); + javaSettingsPanel.addRow(new JLabel(_("options.64BitJavaWarning"))); + javaSettingsPanel.addRow(new JLabel(_("options.minMemory")), minMemorySpinner); + javaSettingsPanel.addRow(new JLabel(_("options.maxMemory")), maxMemorySpinner); + javaSettingsPanel.addRow(new JLabel(_("options.permGen")), permGenSpinner); + SwingHelper.removeOpaqueness(javaSettingsPanel); + tabbedPane.addTab(_("options.javaTab"), SwingHelper.alignTabbedPane(javaSettingsPanel)); + + gameSettingsPanel.addRow(new JLabel(_("options.windowWidth")), widthSpinner); + gameSettingsPanel.addRow(new JLabel(_("options.windowHeight")), heightSpinner); + SwingHelper.removeOpaqueness(gameSettingsPanel); + tabbedPane.addTab(_("options.minecraftTab"), SwingHelper.alignTabbedPane(gameSettingsPanel)); + + proxySettingsPanel.addRow(useProxyCheck); + proxySettingsPanel.addRow(new JLabel(_("options.proxyHost")), proxyHostText); + proxySettingsPanel.addRow(new JLabel(_("options.proxyPort")), proxyPortText); + proxySettingsPanel.addRow(new JLabel(_("options.proxyUsername")), proxyUsernameText); + proxySettingsPanel.addRow(new JLabel(_("options.proxyPassword")), proxyPasswordText); + SwingHelper.removeOpaqueness(proxySettingsPanel); + tabbedPane.addTab(_("options.proxyTab"), SwingHelper.alignTabbedPane(proxySettingsPanel)); + + advancedPanel.addRow(new JLabel(_("options.gameKey")), gameKeyText); + SwingHelper.removeOpaqueness(advancedPanel); + tabbedPane.addTab(_("options.advancedTab"), SwingHelper.alignTabbedPane(advancedPanel)); + + buttonsPanel.addElement(logButton); + buttonsPanel.addGlue(); + buttonsPanel.addElement(okButton); + buttonsPanel.addElement(cancelButton); + + tabContainer.add(tabbedPane, BorderLayout.CENTER); + tabContainer.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + add(tabContainer, BorderLayout.CENTER); + add(buttonsPanel, BorderLayout.SOUTH); + + SwingHelper.equalWidth(okButton, cancelButton); + + cancelButton.addActionListener(ActionListeners.dispose(this)); + + okButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + save(); + } + }); + + logButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ConsoleFrame.showMessages(); + } + }); + } + + /** + * Save the configuration and close the dialog. + */ + public void save() { + mapper.copyFromSwing(); + Persistence.commitAndForget(config); + dispose(); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/ConsoleFrame.java b/launcher/src/main/java/com/skcraft/launcher/dialog/ConsoleFrame.java new file mode 100644 index 0000000..eb3125c --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/ConsoleFrame.java @@ -0,0 +1,156 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.dialog; + +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.swing.LinedBoxPanel; +import com.skcraft.launcher.swing.MessageLog; +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.util.PastebinPoster; +import lombok.Getter; +import lombok.NonNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import static com.skcraft.launcher.util.SharedLocale._; + +/** + * A frame capable of showing messages. + */ +public class ConsoleFrame extends JFrame { + + private static ConsoleFrame globalFrame; + + @Getter private final Image trayRunningIcon; + @Getter private final Image trayClosedIcon; + + @Getter private final MessageLog messageLog; + @Getter private LinedBoxPanel buttonsPanel; + + private boolean registeredGlobalLog = false; + + /** + * Construct the frame. + * + * @param numLines number of lines to show at a time + * @param colorEnabled true to enable a colored console + */ + public ConsoleFrame(int numLines, boolean colorEnabled) { + this(_("console.title"), numLines, colorEnabled); + } + + /** + * Construct the frame. + * + * @param title the title of the window + * @param numLines number of lines to show at a time + * @param colorEnabled true to enable a colored console + */ + public ConsoleFrame(@NonNull String title, int numLines, boolean colorEnabled) { + messageLog = new MessageLog(numLines, colorEnabled); + trayRunningIcon = SwingHelper.readIconImage(Launcher.class, "tray_ok.png"); + trayClosedIcon = SwingHelper.readIconImage(Launcher.class, "tray_closed.png"); + + setTitle(title); + setIconImage(trayRunningIcon); + + setSize(new Dimension(650, 400)); + initComponents(); + + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + performClose(); + } + }); + } + + /** + * Add components to the frame. + */ + private void initComponents() { + JButton pastebinButton = new JButton(_("console.uploadLog")); + buttonsPanel = new LinedBoxPanel(true); + + buttonsPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + buttonsPanel.addElement(pastebinButton); + + add(buttonsPanel, BorderLayout.NORTH); + add(messageLog, BorderLayout.CENTER); + + pastebinButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + pastebinLog(); + } + }); + } + + /** + * Register the global logger if it hasn't been registered. + */ + private void registerLoggerHandler() { + if (!registeredGlobalLog) { + getMessageLog().registerLoggerHandler(); + registeredGlobalLog = true; + } + } + + /** + * Attempt to perform window close. + */ + protected void performClose() { + messageLog.detachGlobalHandler(); + messageLog.clear(); + registeredGlobalLog = false; + dispose(); + } + + /** + * Send the contents of the message log to a pastebin. + */ + private void pastebinLog() { + String text = messageLog.getPastableText(); + // Not really bytes! + messageLog.log(_("console.pasteUploading", text.length()), messageLog.asHighlighted()); + + PastebinPoster.paste(text, new PastebinPoster.PasteCallback() { + @Override + public void handleSuccess(String url) { + messageLog.log(_("console.pasteUploaded", url), messageLog.asHighlighted()); + SwingHelper.openURL(url, messageLog); + } + + @Override + public void handleError(String err) { + messageLog.log(_("console.pasteFailed", err), messageLog.asError()); + } + }); + } + + public static void showMessages() { + ConsoleFrame frame = globalFrame; + if (frame == null) { + frame = new ConsoleFrame(10000, false); + globalFrame = frame; + frame.setTitle(_("console.launcherConsoleTitle")); + frame.registerLoggerHandler(); + frame.setVisible(true); + } else { + frame.setVisible(true); + frame.registerLoggerHandler(); + frame.requestFocus(); + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/FeatureSelectionDialog.java b/launcher/src/main/java/com/skcraft/launcher/dialog/FeatureSelectionDialog.java new file mode 100644 index 0000000..57b0150 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/FeatureSelectionDialog.java @@ -0,0 +1,97 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.dialog; + +import com.skcraft.launcher.model.modpack.Feature; +import com.skcraft.launcher.swing.*; +import lombok.NonNull; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.awt.*; +import java.util.List; + +import static com.skcraft.launcher.util.SharedLocale._; +import static javax.swing.BorderFactory.createEmptyBorder; + +public class FeatureSelectionDialog extends JDialog { + + private final List features; + private final JPanel container = new JPanel(new BorderLayout()); + private final JTextArea descText = new JTextArea(_("features.selectForInfo")); + private final JScrollPane descScroll = new JScrollPane(descText); + private final CheckboxTable componentsTable = new CheckboxTable(); + private final JScrollPane componentsScroll = new JScrollPane(componentsTable); + private final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, componentsScroll, descScroll); + private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); + private final JButton installButton = new JButton(_("features.install")); + + public FeatureSelectionDialog(Window owner, @NonNull List features) { + super(owner, ModalityType.DOCUMENT_MODAL); + + this.features = features; + + setTitle(_("features.title")); + initComponents(); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setSize(new Dimension(500, 400)); + setResizable(false); + setLocationRelativeTo(owner); + } + + private void initComponents() { + componentsTable.setModel(new FeatureTableModel(features)); + + descScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + + descText.setFont(new JLabel().getFont()); + descText.setEditable(false); + descText.setWrapStyleWord(true); + descText.setLineWrap(true); + SwingHelper.removeOpaqueness(descText); + descText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + + splitPane.setDividerLocation(300); + splitPane.setDividerSize(6); + SwingHelper.flattenJSplitPane(splitPane); + + container.setBorder(createEmptyBorder(12, 12, 12, 12)); + container.add(splitPane, BorderLayout.CENTER); + + buttonsPanel.addGlue(); + buttonsPanel.addElement(installButton); + + JLabel descLabel = new JLabel(_("features.intro")); + descLabel.setBorder(createEmptyBorder(12, 12, 4, 12)); + + SwingHelper.equalWidth(installButton, new JButton(_("button.cancel"))); + + add(descLabel, BorderLayout.NORTH); + add(container, BorderLayout.CENTER); + add(buttonsPanel, BorderLayout.SOUTH); + + componentsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + updateDescription(); + } + }); + + installButton.addActionListener(ActionListeners.dispose(this)); + } + + private void updateDescription() { + Feature feature = features.get(componentsTable.getSelectedRow()); + + if (feature != null) { + descText.setText(feature.getDescription()); + } else { + descText.setText(_("features.selectForInfo")); + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/LauncherFrame.java b/launcher/src/main/java/com/skcraft/launcher/dialog/LauncherFrame.java new file mode 100644 index 0000000..f40b40c --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/LauncherFrame.java @@ -0,0 +1,513 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.dialog; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +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.Session; +import com.skcraft.launcher.launch.Runner; +import com.skcraft.launcher.launch.LaunchProcessHandler; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.selfupdate.UpdateChecker; +import com.skcraft.launcher.selfupdate.SelfUpdater; +import com.skcraft.launcher.swing.*; +import com.skcraft.launcher.update.HardResetter; +import com.skcraft.launcher.update.Remover; +import com.skcraft.launcher.update.Updater; +import com.skcraft.launcher.util.SwingExecutor; +import lombok.NonNull; +import lombok.extern.java.Log; +import org.apache.commons.io.FileUtils; + +import javax.swing.*; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Date; +import java.util.logging.Level; + +import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor; +import static com.skcraft.launcher.util.SharedLocale._; + +/** + * The main launcher frame. + */ +@Log +public class LauncherFrame extends JFrame { + + private final Launcher launcher; + + private final HeaderPanel header = new HeaderPanel(); + private final InstanceTable instancesTable = new InstanceTable(); + private final InstanceTableModel instancesModel; + private final JScrollPane instanceScroll = new JScrollPane(instancesTable); + private WebpagePanel webView; + private JSplitPane splitPane; + private final JPanel container = new JPanel(); + private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true).fullyPadded(); + private final JButton launchButton = new JButton(_("launcher.launch")); + private final JButton refreshButton = new JButton(_("launcher.checkForUpdates")); + private final JButton optionsButton = new JButton(_("launcher.options")); + private final JButton selfUpdateButton = new JButton(_("launcher.updateLauncher")); + private final JCheckBox updateCheck = new JCheckBox(_("launcher.downloadUpdates")); + private URL updateUrl; + + /** + * Create a new frame. + * + * @param launcher the launcher + */ + public LauncherFrame(@NonNull Launcher launcher) { + super(_("launcher.title", launcher.getVersion())); + + this.launcher = launcher; + instancesModel = new InstanceTableModel(launcher.getInstances()); + + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + setSize(700, 450); + setMinimumSize(new Dimension(400, 300)); + initComponents(); + setLocationRelativeTo(null); + + SwingHelper.setIconImage(this, Launcher.class, "icon.png"); + + loadInstances(); + checkLauncherUpdate(); + } + + private void initComponents() { + webView = WebpagePanel.forURL(launcher.getNewsURL(), false); + splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, instanceScroll, webView); + selfUpdateButton.setVisible(false); + + updateCheck.setSelected(true); + instancesTable.setModel(instancesModel); + launchButton.setFont(launchButton.getFont().deriveFont(Font.BOLD)); + splitPane.setDividerLocation(200); + splitPane.setDividerSize(4); + SwingHelper.flattenJSplitPane(splitPane); + buttonsPanel.addElement(refreshButton); + buttonsPanel.addElement(updateCheck); + buttonsPanel.addGlue(); + buttonsPanel.addElement(selfUpdateButton); + buttonsPanel.addElement(optionsButton); + buttonsPanel.addElement(launchButton); + container.setLayout(new BorderLayout()); + container.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10)); + container.add(splitPane, BorderLayout.CENTER); + add(buttonsPanel, BorderLayout.SOUTH); + add(container, BorderLayout.CENTER); + + instancesModel.addTableModelListener(new TableModelListener() { + @Override + public void tableChanged(TableModelEvent e) { + if (instancesTable.getRowCount() > 0) { + instancesTable.setRowSelectionInterval(0, 0); + } + } + }); + + instancesTable.addMouseListener(new DoubleClickToButtonAdapter(launchButton)); + + refreshButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + loadInstances(); + checkLauncherUpdate(); + } + }); + + selfUpdateButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + selfUpdate(); + } + }); + + optionsButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showOptions(); + } + }); + + launchButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + launch(); + } + }); + + instancesTable.addMouseListener(new PopupMouseAdapter() { + @Override + protected void showPopup(MouseEvent e) { + int index = instancesTable.rowAtPoint(e.getPoint()); + Instance selected = null; + if (index >= 0) { + instancesTable.setRowSelectionInterval(index, index); + selected = launcher.getInstances().get(index); + } + popupInstanceMenu(e.getComponent(), e.getX(), e.getY(), selected); + } + }); + } + + private void checkLauncherUpdate() { + if (SelfUpdater.updatedAlready) { + return; + } + + ListenableFuture future = launcher.getExecutor().submit(new UpdateChecker(launcher)); + + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(URL result) { + if (result != null) { + requestUpdate(result); + } + } + + @Override + public void onFailure(Throwable t) { + + } + }, SwingExecutor.INSTANCE); + } + + private void selfUpdate() { + URL url = updateUrl; + if (url != null) { + SelfUpdater downloader = new SelfUpdater(launcher, url); + ObservableFuture future = new ObservableFuture( + launcher.getExecutor().submit(downloader), downloader); + + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(File result) { + selfUpdateButton.setVisible(false); + SwingHelper.showMessageDialog( + LauncherFrame.this, + _("launcher.selfUpdateComplete"), + _("launcher.selfUpdateCompleteTitle"), + null, + JOptionPane.INFORMATION_MESSAGE); + } + + @Override + public void onFailure(Throwable t) { + } + }, SwingExecutor.INSTANCE); + + ProgressDialog.showProgress(this, future, _("launcher.selfUpdatingTitle"), _("launcher.selfUpdatingStatus")); + SwingHelper.addErrorDialogCallback(this, future); + } else { + selfUpdateButton.setVisible(false); + } + } + + private void requestUpdate(URL url) { + this.updateUrl = url; + selfUpdateButton.setVisible(true); + } + + /** + * Popup the menu for the instances. + * + * @param component the component + * @param x mouse X + * @param y mouse Y + * @param selected the selected instance, possibly null + */ + private void popupInstanceMenu(Component component, int x, int y, final Instance selected) { + JPopupMenu popup = new JPopupMenu(); + JMenuItem menuItem; + + if (selected != null) { + menuItem = new JMenuItem(!selected.isLocal() ? "Install" : "Launch"); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + launch(); + } + }); + popup.add(menuItem); + + if (selected.isLocal()) { + popup.addSeparator(); + + menuItem = new JMenuItem(_("instance.openFolder")); + menuItem.addActionListener(ActionListeners.browseDir( + LauncherFrame.this, selected.getContentDir(), true)); + popup.add(menuItem); + + menuItem = new JMenuItem(_("instance.openSaves")); + menuItem.addActionListener(ActionListeners.browseDir( + LauncherFrame.this, new File(selected.getContentDir(), "saves"), true)); + popup.add(menuItem); + + menuItem = new JMenuItem(_("instance.openResourcePacks")); + menuItem.addActionListener(ActionListeners.browseDir( + LauncherFrame.this, new File(selected.getContentDir(), "resourcepacks"), true)); + popup.add(menuItem); + + menuItem = new JMenuItem(_("instance.openScreenshots")); + menuItem.addActionListener(ActionListeners.browseDir( + LauncherFrame.this, new File(selected.getContentDir(), "screenshots"), true)); + popup.add(menuItem); + + menuItem = new JMenuItem(_("instance.copyAsPath")); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + File dir = selected.getContentDir(); + dir.mkdirs(); + SwingHelper.setClipboard(dir.getAbsolutePath()); + } + }); + popup.add(menuItem); + + popup.addSeparator(); + + if (!selected.isUpdatePending()) { + menuItem = new JMenuItem(_("instance.forceUpdate")); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + selected.setUpdatePending(true); + launch(); + instancesModel.update(); + } + }); + popup.add(menuItem); + } + + menuItem = new JMenuItem(_("instance.hardForceUpdate")); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + confirmHardUpdate(selected); + } + }); + popup.add(menuItem); + + menuItem = new JMenuItem(_("instance.deleteFiles")); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + confirmDelete(selected); + } + }); + popup.add(menuItem); + } + + popup.addSeparator(); + } + + menuItem = new JMenuItem(_("launcher.refreshList")); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + loadInstances(); + } + }); + popup.add(menuItem); + + popup.show(component, x, y); + + } + + private void confirmDelete(Instance instance) { + if (!SwingHelper.confirmDialog(this, + _("instance.confirmDelete", instance.getTitle()), _("confirmTitle"))) { + return; + } + + // Execute the deleter + Remover resetter = new Remover(instance); + ObservableFuture future = new ObservableFuture( + launcher.getExecutor().submit(resetter), resetter); + + // Show progress + ProgressDialog.showProgress( + this, future, _("instance.deletingTitle"), _("instance.deletingStatus", instance.getTitle())); + SwingHelper.addErrorDialogCallback(this, future); + + // Update the list of instances after updating + future.addListener(new Runnable() { + @Override + public void run() { + loadInstances(); + } + }, SwingExecutor.INSTANCE); + } + + private void confirmHardUpdate(Instance instance) { + if (!SwingHelper.confirmDialog(this, _("instance.confirmHardUpdate"), _("confirmTitle"))) { + return; + } + + // Execute the resetter + HardResetter resetter = new HardResetter(instance); + ObservableFuture future = new ObservableFuture( + launcher.getExecutor().submit(resetter), resetter); + + // Show progress + ProgressDialog.showProgress( this, future, _("instance.resettingTitle"), + _("instance.resettingStatus", instance.getTitle())); + SwingHelper.addErrorDialogCallback(this, future); + + // Update the list of instances after updating + future.addListener(new Runnable() { + @Override + public void run() { + launch(); + instancesModel.update(); + } + }, SwingExecutor.INSTANCE); + } + + private void loadInstances() { + InstanceList.Enumerator loader = launcher.getInstances().createEnumerator(); + ObservableFuture future = new ObservableFuture( + launcher.getExecutor().submit(loader), loader); + + future.addListener(new Runnable() { + @Override + public void run() { + instancesModel.update(); + if (instancesTable.getRowCount() > 0) { + instancesTable.setRowSelectionInterval(0, 0); + } + requestFocus(); + } + }, SwingExecutor.INSTANCE); + + ProgressDialog.showProgress(this, future, _("launcher.checkingTitle"), _("launcher.checkingStatus")); + SwingHelper.addErrorDialogCallback(this, future); + } + + private void showOptions() { + ConfigurationDialog configDialog = new ConfigurationDialog(this, launcher); + configDialog.setVisible(true); + } + + private void launch() { + try { + final Instance instance = launcher.getInstances().get(instancesTable.getSelectedRow()); + boolean update = updateCheck.isSelected() && instance.isUpdatePending(); + + // Store last access date + Date now = new Date(); + instance.setLastAccessed(now); + Persistence.commitAndForget(instance); + + // Perform login + final Session session = LoginDialog.showLoginRequest(this, launcher); + if (session == null) { + return; + } + + // If we have to update, we have to update + if (!instance.isInstalled()) { + update = true; + } + + if (update) { + // Execute the updater + Updater updater = new Updater(launcher, instance); + updater.setOnline(session.isOnline()); + ObservableFuture future = new ObservableFuture( + launcher.getExecutor().submit(updater), updater); + + // Show progress + ProgressDialog.showProgress( + this, future, _("launcher.updatingTitle"), _("launcher.updatingStatus", instance.getTitle())); + SwingHelper.addErrorDialogCallback(this, future); + + // Update the list of instances after updating + future.addListener(new Runnable() { + @Override + public void run() { + instancesModel.update(); + } + }, SwingExecutor.INSTANCE); + + // On success, launch also + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(Instance result) { + launch(instance, session); + } + + @Override + public void onFailure(Throwable t) { + } + }, SwingExecutor.INSTANCE); + } else { + launch(instance, session); + } + } catch (ArrayIndexOutOfBoundsException e) { + SwingHelper.showErrorDialog(this, _("launcher.noInstanceError"), _("launcher.noInstanceTitle")); + } + } + + private void launch(Instance instance, Session session) { + final File extractDir = launcher.createExtractDir(); + + // Get the process + Runner task = new Runner(launcher, instance, session, extractDir); + ObservableFuture processFuture = new ObservableFuture( + launcher.getExecutor().submit(task), task); + + // Show process for the process retrieval + ProgressDialog.showProgress( + this, processFuture, _("launcher.launchingTItle"), _("launcher.launchingStatus", instance.getTitle())); + + // If the process is started, get rid of this window + Futures.addCallback(processFuture, new FutureCallback() { + @Override + public void onSuccess(Process result) { + dispose(); + } + + @Override + public void onFailure(Throwable t) { + } + }); + + // Watch the created process + ListenableFuture future = Futures.transform( + processFuture, new LaunchProcessHandler(launcher), launcher.getExecutor()); + SwingHelper.addErrorDialogCallback(null, future); + + // Clean up at the very end + future.addListener(new Runnable() { + @Override + public void run() { + try { + log.info("Process ended; cleaning up " + extractDir.getAbsolutePath()); + FileUtils.deleteDirectory(extractDir); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to clean up " + extractDir.getAbsolutePath(), e); + } + instancesModel.update(); + } + }, sameThreadExecutor()); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/LoginDialog.java b/launcher/src/main/java/com/skcraft/launcher/dialog/LoginDialog.java new file mode 100644 index 0000000..1d206fa --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/LoginDialog.java @@ -0,0 +1,357 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.dialog; + +import com.google.common.base.Strings; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.skcraft.concurrency.ObservableFuture; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.Configuration; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.auth.*; +import com.skcraft.launcher.swing.*; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.util.SwingExecutor; +import lombok.Getter; +import lombok.NonNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.io.IOException; +import java.util.Date; +import java.util.List; +import java.util.concurrent.Callable; + +import static com.skcraft.launcher.util.SharedLocale._; + +/** + * The login dialog. + */ +public class LoginDialog extends JDialog { + + private final Launcher launcher; + @Getter private final AccountList accounts; + @Getter private Session session; + + private final JComboBox idCombo = new JComboBox(); + private final JPasswordField passwordText = new JPasswordField(); + private final JCheckBox rememberIdCheck = new JCheckBox(_("login.rememberId")); + private final JCheckBox rememberPassCheck = new JCheckBox(_("login.rememberPassword")); + private final JButton loginButton = new JButton(_("login.login")); + private final LinkButton recoverButton = new LinkButton(_("login.recoverAccount")); + private final JButton offlineButton = new JButton(_("login.playOffline")); + private final JButton cancelButton = new JButton(_("button.cancel")); + private final FormPanel formPanel = new FormPanel(); + private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); + + /** + * Create a new login dialog. + * + * @param owner the owner + * @param launcher the launcher + */ + public LoginDialog(Window owner, @NonNull Launcher launcher) { + super(owner, ModalityType.DOCUMENT_MODAL); + + this.launcher = launcher; + this.accounts = launcher.getAccounts(); + + setTitle(_("login.title")); + initComponents(); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setMinimumSize(new Dimension(420, 0)); + setResizable(false); + pack(); + setLocationRelativeTo(owner); + + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + removeListeners(); + dispose(); + } + }); + } + + private void removeListeners() { + idCombo.setModel(new DefaultComboBoxModel()); + } + + private void initComponents() { + idCombo.setModel(getAccounts()); + updateSelection(); + + rememberIdCheck.setBorder(BorderFactory.createEmptyBorder()); + rememberPassCheck.setBorder(BorderFactory.createEmptyBorder()); + idCombo.setEditable(true); + idCombo.getEditor().selectAll(); + + loginButton.setFont(loginButton.getFont().deriveFont(Font.BOLD)); + + formPanel.addRow(new JLabel(_("login.idEmail")), idCombo); + formPanel.addRow(new JLabel(_("login.password")), passwordText); + formPanel.addRow(new JLabel(), rememberIdCheck); + formPanel.addRow(new JLabel(), rememberPassCheck); + buttonsPanel.setBorder(BorderFactory.createEmptyBorder(26, 13, 13, 13)); + + if (launcher.getConfig().isOfflineEnabled()) { + buttonsPanel.addElement(offlineButton); + buttonsPanel.addElement(Box.createHorizontalStrut(2)); + } + buttonsPanel.addElement(recoverButton); + buttonsPanel.addGlue(); + buttonsPanel.addElement(loginButton); + buttonsPanel.addElement(cancelButton); + + add(formPanel, BorderLayout.CENTER); + add(buttonsPanel, BorderLayout.SOUTH); + + getRootPane().setDefaultButton(loginButton); + + passwordText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + + idCombo.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + updateSelection(); + } + }); + + idCombo.getEditor().getEditorComponent().addMouseListener(new PopupMouseAdapter() { + @Override + protected void showPopup(MouseEvent e) { + popupManageMenu(e.getComponent(), e.getX(), e.getY()); + } + }); + + recoverButton.addActionListener( + ActionListeners.openURL(recoverButton, launcher.getProperties().getProperty("resetPasswordUrl"))); + + loginButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + prepareLogin(); + } + }); + + offlineButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setResult(new OfflineSession(launcher.getProperties().getProperty("offlinePlayerName"))); + removeListeners(); + dispose(); + } + }); + + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + removeListeners(); + dispose(); + } + }); + + rememberPassCheck.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (rememberPassCheck.isSelected()) { + rememberIdCheck.setSelected(true); + } + } + }); + + rememberIdCheck.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (!rememberIdCheck.isSelected()) { + rememberPassCheck.setSelected(false); + } + } + }); + } + + private void popupManageMenu(Component component, int x, int y) { + Object selected = idCombo.getSelectedItem(); + JPopupMenu popup = new JPopupMenu(); + JMenuItem menuItem; + + if (selected != null && selected instanceof Account) { + final Account account = (Account) selected; + + menuItem = new JMenuItem(_("login.forgetUser")); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + accounts.remove(account); + Persistence.commitAndForget(accounts); + } + }); + popup.add(menuItem); + + if (!Strings.isNullOrEmpty(account.getPassword())) { + menuItem = new JMenuItem(_("login.forgetPassword")); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + account.setPassword(null); + Persistence.commitAndForget(accounts); + } + }); + popup.add(menuItem); + } + } + + menuItem = new JMenuItem(_("login.forgetAllPasswords")); + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (SwingHelper.confirmDialog(LoginDialog.this, + _("login.confirmForgetAllPasswords"), + _("login.forgetAllPasswordsTitle"))) { + accounts.forgetPasswords(); + Persistence.commitAndForget(accounts); + } + } + }); + popup.add(menuItem); + + popup.show(component, x, y); + } + + private void updateSelection() { + Object selected = idCombo.getSelectedItem(); + + if (selected != null && selected instanceof Account) { + Account account = (Account) selected; + String password = account.getPassword(); + + rememberIdCheck.setSelected(true); + if (!Strings.isNullOrEmpty(password)) { + rememberPassCheck.setSelected(true); + passwordText.setText(password); + } else { + rememberPassCheck.setSelected(false); + } + } else { + passwordText.setText(""); + rememberIdCheck.setSelected(true); + rememberPassCheck.setSelected(false); + } + } + + @SuppressWarnings("deprecation") + private void prepareLogin() { + Object selected = idCombo.getSelectedItem(); + + if (selected != null && selected instanceof Account) { + Account account = (Account) selected; + String password = passwordText.getText(); + + if (password == null || password.isEmpty()) { + SwingHelper.showErrorDialog(this, _("login.noPasswordError"), _("login.noPasswordTitle")); + } else { + if (rememberPassCheck.isSelected()) { + account.setPassword(password); + } else { + account.setPassword(null); + } + + if (rememberIdCheck.isSelected()) { + accounts.add(account); + } else { + accounts.remove(account); + } + + account.setLastUsed(new Date()); + + Persistence.commitAndForget(accounts); + + attemptLogin(account, password); + } + } else { + SwingHelper.showErrorDialog(this, _("login.noLoginError"), _("login.noLoginTitle")); + } + } + + private void attemptLogin(Account account, String password) { + LoginCallable callable = new LoginCallable(account, password); + ObservableFuture future = new ObservableFuture( + launcher.getExecutor().submit(callable), callable); + + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(Session result) { + setResult(result); + } + + @Override + public void onFailure(Throwable t) { + } + }, SwingExecutor.INSTANCE); + + ProgressDialog.showProgress(this, future, _("login.loggingInTitle"), _("login.loggingInStatus")); + SwingHelper.addErrorDialogCallback(this, future); + } + + private void setResult(Session session) { + this.session = session; + removeListeners(); + dispose(); + } + + public static Session showLoginRequest(Window owner, Launcher launcher) { + LoginDialog dialog = new LoginDialog(owner, launcher); + dialog.setVisible(true); + return dialog.getSession(); + } + + private class LoginCallable implements Callable,ProgressObservable { + private final Account account; + private final String password; + + private LoginCallable(Account account, String password) { + this.account = account; + this.password = password; + } + + @Override + public Session call() throws AuthenticationException, IOException, InterruptedException { + LoginService service = launcher.getLoginService(); + List identities = service.login(launcher.getProperties().getProperty("agentName"), account.getId(), password); + + // The list of identities (profiles in Mojang terms) corresponds to whether the account + // owns the game, so we need to check that + if (identities.size() > 0) { + // Set offline enabled flag to true + Configuration config = launcher.getConfig(); + if (!config.isOfflineEnabled()) { + config.setOfflineEnabled(true); + Persistence.commitAndForget(config); + } + + Persistence.commitAndForget(getAccounts()); + return identities.get(0); + } else { + throw new AuthenticationException("Minecraft not owned", _("login.minecraftNotOwnedError")); + } + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return _("login.loggingInStatus"); + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/ProcessConsoleFrame.java b/launcher/src/main/java/com/skcraft/launcher/dialog/ProcessConsoleFrame.java new file mode 100644 index 0000000..7f7ab6d --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/ProcessConsoleFrame.java @@ -0,0 +1,230 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.dialog; + +import com.skcraft.launcher.swing.LinedBoxPanel; +import com.skcraft.launcher.swing.SwingHelper; +import lombok.Getter; +import lombok.Setter; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.PrintWriter; + +import static com.skcraft.launcher.util.SharedLocale._; + +/** + * A version of the console window that can manage a process. + */ +public class ProcessConsoleFrame extends ConsoleFrame { + + private JButton killButton; + private JButton minimizeButton; + private TrayIcon trayIcon; + + @Getter private Process process; + @Getter @Setter private boolean killOnClose; + + private PrintWriter processOut; + + /** + * Create a new instance of the frame. + * + * @param numLines the number of log lines + * @param colorEnabled whether color is enabled in the log + */ + public ProcessConsoleFrame(int numLines, boolean colorEnabled) { + super(_("console.title"), numLines, colorEnabled); + processOut = new PrintWriter( + getMessageLog().getOutputStream(new Color(0, 0, 255)), true); + initComponents(); + updateComponents(); + } + + /** + * Track the given process. + * + * @param process the process + */ + public synchronized void setProcess(Process process) { + try { + Process lastProcess = this.process; + if (lastProcess != null) { + processOut.println(_("console.processEndCode", lastProcess.exitValue())); + } + } catch (IllegalThreadStateException e) { + } + + if (process != null) { + processOut.println(_("console.attachedToProcess")); + } + + this.process = process; + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + updateComponents(); + } + }); + } + + private synchronized boolean hasProcess() { + return process != null; + } + + @Override + protected void performClose() { + if (hasProcess()) { + if (killOnClose) { + performKill(); + } + } + + if (trayIcon != null) { + SystemTray.getSystemTray().remove(trayIcon); + } + + super.performClose(); + } + + private void performKill() { + if (!confirmKill()) { + return; + } + + synchronized (this) { + if (hasProcess()) { + process.destroy(); + setProcess(null); + } + } + + updateComponents(); + } + + protected void initComponents() { + killButton = new JButton(_("console.forceClose")); + minimizeButton = new JButton(); // Text set later + + LinedBoxPanel buttonsPanel = getButtonsPanel(); + buttonsPanel.addGlue(); + buttonsPanel.addElement(killButton); + buttonsPanel.addElement(minimizeButton); + + killButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + performKill(); + } + }); + + minimizeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + contextualClose(); + } + }); + + if (!setupTrayIcon()) { + minimizeButton.setEnabled(true); + } + } + + private boolean setupTrayIcon() { + if (!SystemTray.isSupported()) { + return false; + } + + trayIcon = new TrayIcon(getTrayRunningIcon()); + trayIcon.setImageAutoSize(true); + trayIcon.setToolTip(_("console.trayTooltip")); + + trayIcon.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + reshow(); + } + }); + + PopupMenu popup = new PopupMenu(); + MenuItem item; + + popup.add(item = new MenuItem(_("console.trayTitle"))); + item.setEnabled(false); + + popup.add(item = new MenuItem(_("console.tray.showWindow"))); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + reshow(); + } + }); + + popup.add(item = new MenuItem(_("console.tray.forceClose"))); + item.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + performKill(); + } + }); + + trayIcon.setPopupMenu(popup); + + try { + SystemTray tray = SystemTray.getSystemTray(); + tray.add(trayIcon); + return true; + } catch (AWTException e) { + } + + return false; + } + + private synchronized void updateComponents() { + Image icon = hasProcess() ? getTrayRunningIcon() : getTrayClosedIcon(); + + killButton.setEnabled(hasProcess()); + + if (!hasProcess() || trayIcon == null) { + minimizeButton.setText(_("console.closeWindow")); + } else { + minimizeButton.setText(_("console.hideWindow")); + } + + if (trayIcon != null) { + trayIcon.setImage(icon); + } + + setIconImage(icon); + } + + private synchronized void contextualClose() { + if (!hasProcess() || trayIcon == null) { + performClose(); + } else { + minimize(); + } + + updateComponents(); + } + + private boolean confirmKill() { + return SwingHelper.confirmDialog(this, _("console.confirmKill"), _("console.confirmKillTitle")); + } + + private void minimize() { + setVisible(false); + } + + private void reshow() { + setVisible(true); + requestFocus(); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/ProgressDialog.java b/launcher/src/main/java/com/skcraft/launcher/dialog/ProgressDialog.java new file mode 100644 index 0000000..4cb0320 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/ProgressDialog.java @@ -0,0 +1,244 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.dialog; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.skcraft.concurrency.ObservableFuture; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.swing.LinedBoxPanel; +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.util.SwingExecutor; +import lombok.extern.java.Log; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.ref.WeakReference; +import java.util.Timer; +import java.util.TimerTask; + +import static com.skcraft.launcher.util.SharedLocale._; + +@Log +public class ProgressDialog extends JDialog { + + private static WeakReference lastDialogRef; + + private final String defaultTitle; + private final String defaultMessage; + private final JLabel label = new JLabel(); + private final JPanel progressPanel = new JPanel(new BorderLayout(0, 5)); + private final JPanel textAreaPanel = new JPanel(new BorderLayout()); + private final JProgressBar progressBar = new JProgressBar(); + private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); + private final JTextArea logText = new JTextArea(); + private final JScrollPane logScroll = new JScrollPane(logText); + private final JButton detailsButton = new JButton(); + private final JButton logButton = new JButton(_("progress.viewLog")); + private final JButton cancelButton = new JButton(_("button.cancel")); + + public ProgressDialog(Window owner, String title, String message) { + super(owner, title, ModalityType.DOCUMENT_MODAL); + + setResizable(false); + initComponents(); + label.setText(message); + defaultTitle = title; + defaultMessage = message; + setCompactSize(); + setLocationRelativeTo(owner); + + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + if (confirmCancel()) { + cancel(); + dispose(); + } + } + }); + } + + private void setCompactSize() { + detailsButton.setText(_("progress.details")); + logButton.setVisible(false); + setMinimumSize(new Dimension(400, 100)); + pack(); + } + + private void setDetailsSize() { + detailsButton.setText(_("progress.less")); + logButton.setVisible(true); + setSize(400, 350); + } + + private void initComponents() { + progressBar.setMaximum(1000); + progressBar.setMinimum(0); + progressBar.setIndeterminate(true); + progressBar.setPreferredSize(new Dimension(0, 18)); + + buttonsPanel.addElement(detailsButton); + buttonsPanel.addElement(logButton); + buttonsPanel.addGlue(); + buttonsPanel.addElement(cancelButton); + buttonsPanel.setBorder(BorderFactory.createEmptyBorder(30, 13, 13, 13)); + + logScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + logText.setBackground(getBackground()); + logText.setEditable(false); + logText.setLineWrap(true); + logText.setWrapStyleWord(false); + logText.setFont(new JLabel().getFont()); + + progressPanel.add(label, BorderLayout.NORTH); + progressPanel.setBorder(BorderFactory.createEmptyBorder(13, 13, 0, 13)); + progressPanel.add(progressBar, BorderLayout.CENTER); + textAreaPanel.setBorder(BorderFactory.createEmptyBorder(10, 13, 0, 13)); + textAreaPanel.add(logScroll, BorderLayout.CENTER); + + add(progressPanel, BorderLayout.NORTH); + add(textAreaPanel, BorderLayout.CENTER); + add(buttonsPanel, BorderLayout.SOUTH); + + textAreaPanel.setVisible(false); + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (confirmCancel()) { + cancel(); + dispose(); + } + } + }); + + detailsButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + toggleDetails(); + } + }); + + logButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ConsoleFrame.showMessages(); + } + }); + } + + private boolean confirmCancel() { + return SwingHelper.confirmDialog(this, _("progress.confirmCancel"), _("progress.confirmCancelTitle")); + } + + protected void cancel() { + } + + private void toggleDetails() { + if (textAreaPanel.isVisible()) { + textAreaPanel.setVisible(false); + setCompactSize(); + } else { + textAreaPanel.setVisible(true); + setDetailsSize(); + } + setLocationRelativeTo(getOwner()); + } + + public static void showProgress(final Window owner, final ObservableFuture future, String title, String message) { + final ProgressDialog dialog = new ProgressDialog(owner, title, message) { + @Override + protected void cancel() { + future.cancel(true); + } + }; + + lastDialogRef = new WeakReference(dialog); + + final Timer timer = new Timer(); + timer.scheduleAtFixedRate(new UpdateProgress(dialog, future), 400, 400); + + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(Object result) { + timer.cancel(); + dialog.dispose(); + } + + @Override + public void onFailure(Throwable t) { + timer.cancel(); + dialog.dispose(); + } + }, SwingExecutor.INSTANCE); + + dialog.setVisible(true); + } + + public static ProgressDialog getLastDialog() { + WeakReference ref = lastDialogRef; + if (ref != null) { + return ref.get(); + } + + return null; + } + + private static class UpdateProgress extends TimerTask { + private final ProgressDialog dialog; + private final ProgressObservable observable; + + public UpdateProgress(ProgressDialog dialog, ProgressObservable observable) { + this.dialog = dialog; + this.observable = observable; + } + + @Override + public void run() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + JProgressBar progressBar = dialog.progressBar; + JTextArea logText = dialog.logText; + JLabel label = dialog.label; + + double progress = observable.getProgress(); + if (progress >= 0) { + dialog.setTitle(_("progress.percentTitle", + Math.round(progress * 100 * 100) / 100.0, dialog.defaultTitle)); + progressBar.setValue((int) (progress * 1000)); + progressBar.setIndeterminate(false); + } else { + dialog.setTitle( dialog.defaultTitle); + progressBar.setIndeterminate(true); + } + + String status = observable.getStatus(); + if (status == null) { + status = _("progress.defaultStatus"); + label.setText(dialog.defaultMessage); + } else { + int index = status.indexOf('\n'); + if (index == -1) { + label.setText(status); + } else { + label.setText(status.substring(0, index)); + } + } + logText.setText(status); + logText.setCaretPosition(0); + } + }); + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/Downloader.java b/launcher/src/main/java/com/skcraft/launcher/install/Downloader.java new file mode 100644 index 0000000..137daf1 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/Downloader.java @@ -0,0 +1,21 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import com.skcraft.concurrency.ProgressObservable; + +import java.io.File; +import java.net.URL; +import java.util.List; + + +public interface Downloader extends ProgressObservable { + + File download(List urls, String key, long size, String name); + + File download(URL url, String key, long size, String name); +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/FeatureCache.java b/launcher/src/main/java/com/skcraft/launcher/install/FeatureCache.java new file mode 100644 index 0000000..052ed5d --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/FeatureCache.java @@ -0,0 +1,19 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class FeatureCache { + + private Map selected = new HashMap(); + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/FileCopy.java b/launcher/src/main/java/com/skcraft/launcher/install/FileCopy.java new file mode 100644 index 0000000..34064df --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/FileCopy.java @@ -0,0 +1,47 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import com.google.common.io.Files; +import lombok.NonNull; +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; + +import static com.skcraft.launcher.util.SharedLocale._; + +@Log +public class FileCopy implements InstallTask { + + private final File from; + private final File to; + + public FileCopy(@NonNull File from, @NonNull File to) { + this.from = from; + this.to = to; + } + + @Override + public void execute() throws IOException { + log.log(Level.INFO, "Copying to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()}); + to.getParentFile().mkdirs(); + Files.copy(from, to); + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return _("installer.copyingFile", from, to); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/FileMover.java b/launcher/src/main/java/com/skcraft/launcher/install/FileMover.java new file mode 100644 index 0000000..3293eb6 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/FileMover.java @@ -0,0 +1,47 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import lombok.NonNull; +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; + +import static com.skcraft.launcher.util.SharedLocale._; + +@Log +public class FileMover implements InstallTask { + + private final File from; + private final File to; + + public FileMover(@NonNull File from, @NonNull File to) { + this.from = from; + this.to = to; + } + + @Override + public void execute() throws IOException { + log.log(Level.INFO, "Moving to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()}); + to.getParentFile().mkdirs(); + to.delete(); + from.renameTo(to); + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return _("installer.movingFile", from, to); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/HttpDownloader.java b/launcher/src/main/java/com/skcraft/launcher/install/HttpDownloader.java new file mode 100644 index 0000000..840ede4 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/HttpDownloader.java @@ -0,0 +1,280 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import com.google.common.base.Charsets; +import com.google.common.base.Strings; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.util.HttpRequest; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.logging.Level; + +import static com.skcraft.launcher.util.SharedLocale._; + +@Log +public class HttpDownloader implements Downloader { + + private final Random random = new Random(); + private final HashFunction hf = Hashing.sha1(); + + private final File tempDir; + @Getter @Setter private int threadCount = 6; + @Getter @Setter private int retryDelay = 2000; + @Getter @Setter private int tryCount = 3; + + private List queue = new ArrayList(); + private final Set usedKeys = new HashSet(); + + private final List running = new ArrayList(); + private final List failed = new ArrayList(); + private long downloaded = 0; + private long total = 0; + private int left = 0; + + /** + * Create a new downloader using the given executor. + * + * @param tempDir the temporary directory + */ + public HttpDownloader(@NonNull File tempDir) { + this.tempDir = tempDir; + } + + /** + * Make sure that we aren't re-using hash IDs. + * + * @param baseKey the key to make unique + * @return a unique key + */ + private String createUniqueKey(String baseKey) { + String key = baseKey; + int i = 0; + while (usedKeys.contains(key)) { + key = baseKey + "_" + (i++); + } + usedKeys.add(key); + return key; + } + + @Override + public synchronized File download(@NonNull List urls, @NonNull String key, long size, String name) { + if (urls.isEmpty()) { + throw new IllegalArgumentException("Can't download empty list of URLs"); + } + + String hash = hf.hashString(Strings.nullToEmpty(key) + urls.get(0), Charsets.UTF_8).toString(); + hash = createUniqueKey(hash); + File tempFile = new File(tempDir, hash.substring(0, 2) + "/" + hash); + + // If the file is already downloaded (such as from before), then don't re-download + if (!tempFile.exists()) { + total += size; + left++; + queue.add(new HttpDownloadJob(tempFile, urls, size, name != null ? name : tempFile.getName())); + } + + return tempFile; + } + + + @Override + public File download(URL url, String key, long size, String name) { + List urls = new ArrayList(); + urls.add(url); + return download(urls, key, size, name); + } + + /** + * Prevent further downloads from being queued and download queued files. + * + * @throws InterruptedException thrown on interruption + * @throws IOException thrown on I/O error + */ + public void execute() throws InterruptedException, IOException { + synchronized (this) { + queue = Collections.unmodifiableList(queue); + } + + ListeningExecutorService executor = MoreExecutors.listeningDecorator( + Executors.newFixedThreadPool(threadCount)); + + try { + List> futures = new ArrayList>(); + + synchronized (this) { + for (HttpDownloadJob job : queue) { + futures.add(executor.submit(job)); + } + } + + try { + Futures.allAsList(futures).get(); + } catch (ExecutionException e) { + throw new IOException("Something went wrong", e); + } + + synchronized (this) { + if (failed.size() > 0) { + throw new IOException(failed.size() + " file(s) could not be downloaded"); + } + } + } finally { + executor.shutdownNow(); + } + } + + @Override + public synchronized double getProgress() { + if (total <= 0) { + return -1; + } + + long downloaded = this.downloaded; + for (HttpDownloadJob job : running) { + downloaded += Math.max(0, job.getProgress() * job.size); + } + return downloaded / (double) total; + } + + @Override + public synchronized String getStatus() { + String failMessage = _("downloader.failedCount", failed.size()); + if (running.size() == 1) { + return _("downloader.downloadingItem", running.get(0).getName()) + + "\n" + running.get(0).getStatus() + + "\n" + failMessage; + } else if (running.size() > 0) { + StringBuilder builder = new StringBuilder(); + for (HttpDownloadJob job : running) { + builder.append("\n"); + builder.append(job.getStatus()); + } + return _("downloader.downloadingList", queue.size(), left, failed.size()) + + builder.toString() + + "\n" + failMessage; + } else { + return _("downloader.noDownloads"); + } + } + + public class HttpDownloadJob implements Runnable, ProgressObservable { + private final File destFile; + private final List urls; + private final long size; + @Getter private String name; + private HttpRequest request; + + private HttpDownloadJob(File destFile, List urls, long size, String name) { + this.destFile = destFile; + this.urls = urls; + this.size = size; + this.name = name; + } + + @Override + public void run() { + try { + synchronized (HttpDownloader.this) { + running.add(this); + } + + download(); + + synchronized (HttpDownloader.this) { + downloaded += size; + } + } catch (IOException e) { + synchronized (HttpDownloader.this) { + failed.add(this); + } + } catch (InterruptedException e) { + log.info("Download of " + destFile + " was interrupted"); + } finally { + synchronized (HttpDownloader.this) { + left--; + running.remove(this); + } + } + } + + private void download() throws IOException, InterruptedException { + log.log(Level.INFO, "Downloading " + destFile + " from " + urls); + + File destDir = destFile.getParentFile(); + File tempFile = new File(destDir, destFile.getName() + ".tmp"); + destDir.mkdirs(); + + // Try to download + download(tempFile); + + destFile.delete(); + if (!tempFile.renameTo(destFile)) { + throw new IOException(String.format("Failed to rename %s to %s", tempFile, destFile)); + } + } + + private void download(File file) throws IOException, InterruptedException { + int trial = 0; + boolean first = true; + IOException lastException = null; + + do { + for (URL url : urls) { + // Sleep between each trial + if (!first) { + Thread.sleep((long) (retryDelay / 2 + (random.nextDouble() * retryDelay))); + } + first = false; + + try { + request = HttpRequest.get(url); + request.execute().expectResponseCode(200).saveContent(file); + return; + } catch (IOException e) { + lastException = e; + log.log(Level.WARNING, "Failed to download " + url, e); + } + } + } while (++trial < tryCount); + + throw new IOException("Failed to download from " + urls, lastException); + } + + @Override + public double getProgress() { + HttpRequest request = this.request; + return request != null ? request.getProgress() : -1; + } + + @Override + public String getStatus() { + double progress = getProgress(); + if (progress >= 0) { + return _("downloader.jobProgress", name, Math.round(progress * 100 * 100) / 100.0); + } else { + return _("downloader.jobPending", name); + } + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/InstallLog.java b/launcher/src/main/java/com/skcraft/launcher/install/InstallLog.java new file mode 100644 index 0000000..7398200 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/InstallLog.java @@ -0,0 +1,87 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import lombok.NonNull; + +import java.io.File; +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Data +public class InstallLog { + + @JsonIgnore + private File baseDir; + private Map> entries = new HashMap>(); + @JsonIgnore + private Set cache = new HashSet(); + + public synchronized void add(@NonNull String group, @NonNull String entry) { + cache.add(entry); + Set subEntries = entries.get(group); + if (subEntries == null) { + subEntries = new HashSet(); + entries.put(group, subEntries); + } + subEntries.add(entry); + } + + public synchronized void add(@NonNull File group, @NonNull File entry) { + add(relativize(group), relativize(entry)); + } + + public synchronized boolean has(@NonNull String entry) { + return cache.contains(entry); + } + + public synchronized boolean has(@NonNull File entry) { + return has(relativize(entry)); + } + + public synchronized boolean copyGroupFrom(InstallLog other, String group) { + Set otherSet = other.entries.get(group); + if (otherSet == null) { + return false; + } + for (String entry : otherSet) { + add(group, entry); + } + return true; + } + + public synchronized boolean copyGroupFrom(@NonNull InstallLog other, @NonNull File entry) { + return copyGroupFrom(other, relativize(entry)); + } + + @JsonIgnore + public synchronized Set>> getEntrySet() { + return entries.entrySet(); + } + + public synchronized boolean hasGroup(String group) { + return entries.containsKey(group); + } + + private String relativize(File child) { + checkNotNull(baseDir); + URI uri = child.toURI(); + String relative = baseDir.toURI().relativize(uri).getPath(); + if (relative.equals(uri.toString())) { + throw new IllegalArgumentException("Child path not in base"); + } + return relative; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/InstallLogFileMover.java b/launcher/src/main/java/com/skcraft/launcher/install/InstallLogFileMover.java new file mode 100644 index 0000000..a34c093 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/InstallLogFileMover.java @@ -0,0 +1,50 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import lombok.NonNull; +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; + +import static com.skcraft.launcher.util.SharedLocale._; + +@Log +public class InstallLogFileMover implements InstallTask { + + private final InstallLog installLog; + private final File from; + private final File to; + + public InstallLogFileMover(InstallLog installLog, @NonNull File from, @NonNull File to) { + this.installLog = installLog; + this.from = from; + this.to = to; + } + + @Override + public void execute() throws IOException { + InstallLogFileMover.log.log(Level.INFO, "Installing to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()}); + to.getParentFile().mkdirs(); + to.delete(); + from.renameTo(to); + installLog.add(to, to); + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return _("installer.movingFile", from, to); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/InstallTask.java b/launcher/src/main/java/com/skcraft/launcher/install/InstallTask.java new file mode 100644 index 0000000..8988002 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/InstallTask.java @@ -0,0 +1,15 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import com.skcraft.concurrency.ProgressObservable; + +public interface InstallTask extends ProgressObservable { + + void execute() throws Exception; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/Installer.java b/launcher/src/main/java/com/skcraft/launcher/install/Installer.java new file mode 100644 index 0000000..0ab03d2 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/Installer.java @@ -0,0 +1,85 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import com.skcraft.concurrency.ProgressObservable; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.skcraft.launcher.LauncherUtils.checkInterrupted; +import static com.skcraft.launcher.util.SharedLocale._; + +@Log +public class Installer implements ProgressObservable { + + @Getter private final File tempDir; + private final HttpDownloader downloader; + private InstallTask running; + private int count = 0; + private int finished = 0; + + private List queue = new ArrayList(); + + public Installer(@NonNull File tempDir) { + this.tempDir = tempDir; + this.downloader = new HttpDownloader(tempDir); + } + + public synchronized void queue(@NonNull InstallTask runnable) { + queue.add(runnable); + count++; + } + + public void download() throws IOException, InterruptedException { + downloader.execute(); + } + + public synchronized void execute() throws Exception { + queue = Collections.unmodifiableList(queue); + + try { + for (InstallTask runnable : queue) { + checkInterrupted(); + running = runnable; + runnable.execute(); + finished++; + } + } finally { + running = null; + } + } + + public Downloader getDownloader() { + return downloader; + } + + @Override + public double getProgress() { + return finished / (double) count; + } + + @Override + public String getStatus() { + InstallTask running = this.running; + if (running != null) { + String status = running.getStatus(); + if (status == null) { + status = running.toString(); + } + return _("installer.executing", count - finished) + "\n" + status; + } else { + return _("installer.installing"); + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/UpdateCache.java b/launcher/src/main/java/com/skcraft/launcher/install/UpdateCache.java new file mode 100644 index 0000000..a33d9d8 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/UpdateCache.java @@ -0,0 +1,29 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import lombok.Data; +import lombok.NonNull; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class UpdateCache { + + private Map cache = new HashMap(); + + public synchronized boolean mark(@NonNull String key, @NonNull String version) { + String current = cache.get(key); + if (current != null && version.equals(current)) { + return false; + } else { + cache.put(key, version); + return true; + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/install/ZipExtract.java b/launcher/src/main/java/com/skcraft/launcher/install/ZipExtract.java new file mode 100644 index 0000000..9cc8c48 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/install/ZipExtract.java @@ -0,0 +1,102 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.install; + +import com.google.common.io.ByteSource; +import com.google.common.io.Closer; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.apache.commons.io.IOUtils; + +import java.io.*; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import static org.apache.commons.io.IOUtils.closeQuietly; + +public class ZipExtract implements Runnable { + + @Getter private final ByteSource source; + @Getter private final File destination; + @Getter @Setter + private List exclude; + + public ZipExtract(@NonNull ByteSource source, @NonNull File destination) { + this.source = source; + this.destination = destination; + } + + @Override + public void run() { + Closer closer = Closer.create(); + + try { + InputStream is = closer.register(source.openBufferedStream()); + ZipInputStream zis = closer.register(new ZipInputStream(is)); + ZipEntry entry; + + destination.getParentFile().mkdirs(); + + while ((entry = zis.getNextEntry()) != null) { + if (matches(entry)) { + File file = new File(getDestination(), entry.getName()); + writeEntry(zis, file); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + closer.close(); + } catch (IOException e) { + } + } + } + + /** + * Checks if the given entry should be extracted. + * + * @param entry the entry + * @return true if the entry matches the filter + */ + private boolean matches(ZipEntry entry) { + if (exclude != null) { + for (String pattern : exclude) { + if (entry.getName().startsWith(pattern)) { + return false; + } + } + } + + return true; + } + + private void writeEntry(ZipInputStream zis, File path) throws IOException { + FileOutputStream fos = null; + BufferedOutputStream bos = null; + + try { + path.getParentFile().mkdirs(); + + fos = new FileOutputStream(path); + bos = new BufferedOutputStream(fos); + IOUtils.copy(zis, bos); + } finally { + closeQuietly(bos); + closeQuietly(fos); + } + } + + @Override + public String toString() { + return destination.getName(); + } + + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/JavaProcessBuilder.java b/launcher/src/main/java/com/skcraft/launcher/launch/JavaProcessBuilder.java new file mode 100644 index 0000000..726cdbf --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/launch/JavaProcessBuilder.java @@ -0,0 +1,138 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.launch; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A tool to build the entire command line used to launch a Java process. + * It combines flags, memory settings, arguments, the class path, and + * the main class. + */ +@ToString +public class JavaProcessBuilder { + + private static final Pattern argsPattern = Pattern.compile("(?:([^\"]\\S*)|\"(.+?)\")\\s*"); + + @Getter @Setter private File jvmPath = JavaRuntimeFinder.findBestJavaPath(); + @Getter @Setter private int minMemory; + @Getter @Setter private int maxMemory; + @Getter @Setter private int permGen; + + @Getter private final List classPath = new ArrayList(); + @Getter private final List flags = new ArrayList(); + @Getter private final List args = new ArrayList(); + @Getter @Setter private String mainClass; + + public void tryJvmPath(File path) throws IOException { + // Try the parent directory + if (!path.exists()) { + throw new IOException( + "The configured Java runtime path '" + path + "' doesn't exist."); + } else if (path.isFile()) { + path = path.getParentFile(); + } + + File binDir = new File(path, "bin"); + if (binDir.isDirectory()) { + path = binDir; + } + + setJvmPath(path); + } + + public JavaProcessBuilder classPath(File file) { + getClassPath().add(file); + return this; + } + + public JavaProcessBuilder classPath(String path) { + getClassPath().add(new File(path)); + return this; + } + + public String buildClassPath() { + StringBuilder builder = new StringBuilder(); + boolean first = true; + + for (File file : classPath) { + if (first) { + first = false; + } else { + builder.append(File.pathSeparator); + } + + builder.append(file.getAbsolutePath()); + } + + return builder.toString(); + } + + public List buildCommand() { + List command = new ArrayList(); + + if (getJvmPath() != null) { + command.add(getJvmPath() + File.separator + "java"); + } else { + command.add("java"); + } + + for (String flag : flags) { + command.add(flag); + } + + if (minMemory > 0) { + command.add("-Xms" + String.valueOf(minMemory) + "M"); + } + + if (maxMemory > 0) { + command.add("-Xmx" + String.valueOf(maxMemory) + "M"); + } + + if (permGen > 0) { + command.add("-XX:MaxPermSize=" + String.valueOf(permGen) + "M"); + } + + command.add("-cp"); + command.add(buildClassPath()); + + command.add(mainClass); + + for (String arg : args) { + command.add(arg); + } + + return command; + } + + /** + * Split the given string as simple command line arguments. + * + *

This is not to be used for security purposes.

+ * + * @param str the string + * @return the split args + */ + public static List splitArgs(String str) { + Matcher matcher = argsPattern.matcher(str); + List parts = new ArrayList(); + while (matcher.find()) { + parts.add(matcher.group(1)); + } + return parts; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java b/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java new file mode 100644 index 0000000..6926176 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java @@ -0,0 +1,136 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.launch; + +import com.skcraft.launcher.util.Environment; +import com.skcraft.launcher.util.Platform; +import com.skcraft.launcher.util.WinRegistry; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Finds the best Java runtime to use. + */ +public final class JavaRuntimeFinder { + + private JavaRuntimeFinder() { + } + + /** + * Return the path to the best found JVM location. + * + * @return the JVM location, or null + */ + public static File findBestJavaPath() { + if (Environment.getInstance().getPlatform() != Platform.WINDOWS) { + return null; + } + + List entries = new ArrayList(); + try { + getEntriesFromRegistry(entries, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); + getEntriesFromRegistry(entries, "SOFTWARE\\JavaSoft\\Java Development Kit"); + } catch (Throwable e) { + } + Collections.sort(entries); + + if (entries.size() > 0) { + return new File(entries.get(0).dir, "bin"); + } + + return null; + } + + private static void getEntriesFromRegistry(List entries, String basePath) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + List subKeys = WinRegistry.readStringSubKeys( + WinRegistry.HKEY_LOCAL_MACHINE, basePath); + for (String subKey : subKeys) { + JREEntry entry = getEntryFromRegistry(basePath, subKey); + if (entry != null) { + entries.add(entry); + } + } + } + + private static JREEntry getEntryFromRegistry(String basePath, String version) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + String regPath = basePath + "\\" + version; + String path = WinRegistry.readString( + WinRegistry.HKEY_LOCAL_MACHINE, regPath, "JavaHome"); + File dir = new File(path); + if (dir.exists() && new File(dir, "bin/java.exe").exists()) { + JREEntry entry = new JREEntry(); + entry.dir = dir; + entry.version = version; + entry.is64Bit = guessIf64Bit(dir); + return entry; + } else { + return null; + } + } + + private static boolean guessIf64Bit(File path) { + String programFilesX86 = System.getenv("ProgramFiles(x86)"); + if (programFilesX86 == null) { + return true; + } + return !path.toString().startsWith(new File(programFilesX86).toString()); + } + + private static class JREEntry implements Comparable { + private File dir; + private String version; + private boolean is64Bit; + + @Override + public int compareTo(JREEntry o) { + if (is64Bit && !o.is64Bit) { + return -1; + } else if (!is64Bit && o.is64Bit) { + return 1; + } + + String[] a = version.split("[\\._]"); + String[] b = o.version.split("[\\._]"); + int min = Math.min(a.length, b.length); + + for (int i = 0; i < min; i++) { + int first, second; + + try { + first = Integer.parseInt(a[i]); + } catch (NumberFormatException e) { + return -1; + } + + try { + second = Integer.parseInt(b[i]); + } catch (NumberFormatException e) { + return 1; + } + + if (first > second) { + return -1; + } else if (first < second) { + return 1; + } + } + + if (a.length == b.length) { + return 0; // Same + } + + return a.length > b.length ? -1 : 1; + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/LaunchProcessHandler.java b/launcher/src/main/java/com/skcraft/launcher/launch/LaunchProcessHandler.java new file mode 100644 index 0000000..0e4934e --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/launch/LaunchProcessHandler.java @@ -0,0 +1,79 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.launch; + +import com.google.common.base.Function; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.dialog.LauncherFrame; +import com.skcraft.launcher.dialog.ProcessConsoleFrame; +import com.skcraft.launcher.swing.MessageLog; +import lombok.NonNull; +import lombok.extern.java.Log; + +import javax.swing.*; +import java.lang.reflect.InvocationTargetException; +import java.util.logging.Level; + +/** + * Handles post-process creation during launch. + */ +@Log +public class LaunchProcessHandler implements Function { + + private static final int CONSOLE_NUM_LINES = 10000; + + private final Launcher launcher; + private ProcessConsoleFrame consoleFrame; + + public LaunchProcessHandler(@NonNull Launcher launcher) { + this.launcher = launcher; + } + + @Override + public ProcessConsoleFrame apply(final Process process) { + log.info("Watching process " + process); + + try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + consoleFrame = new ProcessConsoleFrame(CONSOLE_NUM_LINES, true); + consoleFrame.setProcess(process); + consoleFrame.setVisible(true); + MessageLog messageLog = consoleFrame.getMessageLog(); + messageLog.consume(process.getInputStream()); + messageLog.consume(process.getErrorStream()); + } + }); + + // Wait for the process to end + process.waitFor(); + } catch (InterruptedException e) { + // Orphan process + } catch (InvocationTargetException e) { + log.log(Level.WARNING, "Unexpected failure", e); + } + + log.info("Process ended, re-showing launcher..."); + + // Restore the launcher + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + new LauncherFrame(launcher).setVisible(true); + + if (consoleFrame != null) { + consoleFrame.setProcess(null); + consoleFrame.requestFocus(); + } + } + }); + + return consoleFrame; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java b/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java new file mode 100644 index 0000000..7f5d426 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/launch/Runner.java @@ -0,0 +1,360 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.launch; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Strings; +import com.google.common.io.Files; +import com.skcraft.concurrency.DefaultProgress; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.*; +import com.skcraft.launcher.auth.Session; +import com.skcraft.launcher.install.ZipExtract; +import com.skcraft.launcher.model.minecraft.AssetsIndex; +import com.skcraft.launcher.model.minecraft.Library; +import com.skcraft.launcher.model.minecraft.VersionManifest; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.util.Environment; +import com.skcraft.launcher.util.Platform; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.extern.java.Log; +import org.apache.commons.lang.text.StrSubstitutor; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import static com.skcraft.launcher.LauncherUtils.checkInterrupted; +import static com.skcraft.launcher.util.SharedLocale._; + +/** + * Handles the launching of an instance. + */ +@Log +public class Runner implements Callable, ProgressObservable { + + private ProgressObservable progress = new DefaultProgress(0, _("runner.preparing")); + + private final ObjectMapper mapper = new ObjectMapper(); + private final Launcher launcher; + private final Instance instance; + private final Session session; + private final File extractDir; + @Getter @Setter private Environment environment = Environment.getInstance(); + + private VersionManifest versionManifest; + private AssetsIndex assetsIndex; + private File virtualAssetsDir; + private Configuration config; + private JavaProcessBuilder builder; + private AssetsRoot assetsRoot; + + /** + * Create a new instance launcher. + * + * @param launcher the launcher + * @param instance the instance + * @param session the session + * @param extractDir the directory to extract to + */ + public Runner(@NonNull Launcher launcher, @NonNull Instance instance, + @NonNull Session session, @NonNull File extractDir) { + this.launcher = launcher; + this.instance = instance; + this.session = session; + this.extractDir = extractDir; + } + + /** + * Get the path to the JAR. + * + * @return the JAR path + */ + private File getJarPath() { + File jarPath = instance.getCustomJarPath(); + if (!jarPath.exists()) { + jarPath = launcher.getJarPath(versionManifest); + } + return jarPath; + } + + @Override + public Process call() throws Exception { + if (!instance.isInstalled()) { + throw new LauncherException("Update required", _("runner.updateRequired")); + } + + config = launcher.getConfig(); + builder = new JavaProcessBuilder(); + assetsRoot = launcher.getAssets(); + + // Load manifiests + versionManifest = mapper.readValue(instance.getVersionPath(), VersionManifest.class); + + // Load assets index + File assetsFile = assetsRoot.getIndexPath(versionManifest); + try { + assetsIndex = mapper.readValue(assetsFile, AssetsIndex.class); + } catch (FileNotFoundException e) { + instance.setInstalled(false); + Persistence.commitAndForget(instance); + throw new LauncherException("Missing assets index " + assetsFile.getAbsolutePath(), + _("runner.missingAssetsIndex", instance.getTitle(), assetsFile.getAbsolutePath())); + } catch (IOException e) { + instance.setInstalled(false); + Persistence.commitAndForget(instance); + throw new LauncherException("Corrupt assets index " + assetsFile.getAbsolutePath(), + _("runner.corruptAssetsIndex", instance.getTitle(), assetsFile.getAbsolutePath())); + } + + // Copy over assets to the tree + try { + AssetsRoot.AssetsTreeBuilder assetsBuilder = assetsRoot.createAssetsBuilder(versionManifest); + progress = assetsBuilder; + virtualAssetsDir = assetsBuilder.build(); + } catch (LauncherException e) { + instance.setInstalled(false); + Persistence.commitAndForget(instance); + throw e; + } + + progress = new DefaultProgress(0.9, _("runner.collectingArgs")); + + addJvmArgs(); + addLibraries(); + addJarArgs(); + addProxyArgs(); + addWindowArgs(); + addPlatformArgs(); + + builder.classPath(getJarPath()); + builder.setMainClass(versionManifest.getMainClass()); + + callLaunchModifier(); + + ProcessBuilder processBuilder = new ProcessBuilder(builder.buildCommand()); + processBuilder.directory(instance.getContentDir()); + Runner.log.info("Launching: " + builder); + checkInterrupted(); + + progress = new DefaultProgress(1, _("runner.startingJava")); + + return processBuilder.start(); + } + + /** + * Call the manifest launch modifier. + */ + private void callLaunchModifier() { + instance.modify(builder); + } + + /** + * Add platform-specific arguments. + */ + private void addPlatformArgs() { + // Mac OS X arguments + if (getEnvironment().getPlatform() == Platform.MAC_OS_X) { + File icnsPath = assetsIndex.getObjectPath(assetsRoot, "icons/minecraft.icns"); + if (icnsPath != null) { + builder.getFlags().add("-Xdock:icon=" + icnsPath.getAbsolutePath()); + builder.getFlags().add("-Xdock:name=Minecraft"); + } + } + + // Windows arguments + if (getEnvironment().getPlatform() == Platform.WINDOWS) { + builder.getFlags().add("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"); + } + } + + /** + * Add libraries. + */ + private void addLibraries() throws LauncherException { + // Add libraries to classpath or extract the libraries as necessary + for (Library library : versionManifest.getLibraries()) { + if (!library.matches(environment)) { + continue; + } + + File path = new File(launcher.getLibrariesDir(), library.getPath(environment)); + + if (path.exists()) { + Library.Extract extract = library.getExtract(); + if (extract != null) { + ZipExtract zipExtract = new ZipExtract(Files.asByteSource(path), extractDir); + zipExtract.setExclude(extract.getExclude()); + zipExtract.run(); + } else { + builder.classPath(path); + } + } else { + instance.setInstalled(false); + Persistence.commitAndForget(instance); + throw new LauncherException("Missing library " + library.getName(), + _("runner.missingLibrary", instance.getTitle(), library.getName())); + } + } + + builder.getFlags().add("-Djava.library.path=" + extractDir.getAbsoluteFile()); + } + + /** + * Add JVM arguments. + * + * @throws IOException on I/O error + */ + private void addJvmArgs() throws IOException { + int minMemory = config.getMinMemory(); + int maxMemory = config.getMaxMemory(); + int permGen = config.getPermGen(); + + if (minMemory <= 0) { + minMemory = 1024; + } + + if (maxMemory <= 0) { + maxMemory = 1024; + } + + if (permGen <= 0) { + permGen = 128; + } + + if (permGen <= 64) { + permGen = 64; + } + + if (minMemory > maxMemory) { + maxMemory = minMemory; + } + + builder.setMinMemory(minMemory); + builder.setMaxMemory(maxMemory); + builder.setPermGen(permGen); + + String rawJvmPath = config.getJvmPath(); + if (!Strings.isNullOrEmpty(rawJvmPath)) { + builder.tryJvmPath(new File(rawJvmPath)); + } + + String rawJvmArgs = config.getJvmArgs(); + if (!Strings.isNullOrEmpty(rawJvmArgs)) { + List flags = builder.getFlags(); + + for (String arg : JavaProcessBuilder.splitArgs(rawJvmArgs)) { + flags.add(arg); + } + } + } + + /** + * Add arguments for the application. + * + * @throws JsonProcessingException on error + */ + private void addJarArgs() throws JsonProcessingException { + List args = builder.getArgs(); + + String[] rawArgs = versionManifest.getMinecraftArguments().split(" +"); + StrSubstitutor substitutor = new StrSubstitutor(getCommandSubstitutions()); + for (String arg : rawArgs) { + args.add(substitutor.replace(arg)); + } + } + + /** + * Add proxy arguments. + */ + private void addProxyArgs() { + List args = builder.getArgs(); + + if (config.isProxyEnabled()) { + String host = config.getProxyHost(); + int port = config.getProxyPort(); + String username = config.getProxyUsername(); + String password = config.getProxyPassword(); + + if (!Strings.isNullOrEmpty(host) && port > 0 && port < 65535) { + args.add("--proxyHost"); + args.add(config.getProxyHost()); + args.add("--proxyPort"); + args.add(String.valueOf(port)); + + if (!Strings.isNullOrEmpty(username)) { + builder.getArgs().add("--proxyUser"); + builder.getArgs().add(username); + builder.getArgs().add("--proxyPass"); + builder.getArgs().add(password); + } + } + } + } + + /** + * Add window arguments. + */ + private void addWindowArgs() { + List args = builder.getArgs(); + int width = config.getWindowWidth(); + int height = config.getWidowHeight(); + + if (width >= 10) { + args.add("--width"); + args.add(String.valueOf(width)); + args.add("--height"); + args.add(String.valueOf(height)); + } + } + + /** + * Build the list of command substitutions. + * + * @return the map of substitutions + * @throws JsonProcessingException on error + */ + private Map getCommandSubstitutions() throws JsonProcessingException { + Map map = new HashMap(); + + map.put("version_name", versionManifest.getId()); + + map.put("auth_access_token", session.getAccessToken()); + map.put("auth_session", session.getSessionToken()); + map.put("auth_player_name", session.getName()); + map.put("auth_uuid", session.getUuid()); + + map.put("profile_name", session.getName()); + map.put("user_type", session.getUserType().getName()); + map.put("user_properties", mapper.writeValueAsString(session.getUserProperties())); + + map.put("game_directory", instance.getContentDir().getAbsolutePath()); + map.put("game_assets", virtualAssetsDir.getAbsolutePath()); + map.put("assets_root", launcher.getAssets().getDir().getAbsolutePath()); + map.put("assets_index_name", versionManifest.getAssetsIndex()); + + return map; + } + + @Override + public double getProgress() { + return progress.getProgress(); + } + + @Override + public String getStatus() { + return progress.getStatus(); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallData.java b/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallData.java new file mode 100644 index 0000000..c346dc6 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallData.java @@ -0,0 +1,19 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.loader; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class InstallData { + + private String path; + private String filePath; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java b/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java new file mode 100644 index 0000000..6205501 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java @@ -0,0 +1,21 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.loader; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class InstallProfile { + + @JsonProperty("install") + private InstallData installData; + private VersionInfo versionInfo; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java b/launcher/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java new file mode 100644 index 0000000..f9872c4 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java @@ -0,0 +1,23 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.loader; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.skcraft.launcher.model.minecraft.Library; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class VersionInfo { + + private String minecraftArguments; + private String mainClass; + private List libraries; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Asset.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Asset.java new file mode 100644 index 0000000..122e9ce --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Asset.java @@ -0,0 +1,19 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.minecraft; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Asset { + + private String hash; + private int size; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/AssetsIndex.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/AssetsIndex.java new file mode 100644 index 0000000..53f56a0 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/AssetsIndex.java @@ -0,0 +1,33 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.minecraft; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.skcraft.launcher.AssetsRoot; +import lombok.Data; +import lombok.NonNull; + +import java.io.File; +import java.util.Map; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class AssetsIndex { + + private boolean virtual; + private Map objects; + + public File getObjectPath(@NonNull AssetsRoot assetsRoot, @NonNull String name) { + Asset asset = objects.get(name); + if (asset != null) { + return assetsRoot.getObjectPath(asset); + } else { + return null; + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Library.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Library.java new file mode 100644 index 0000000..ccb13de --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Library.java @@ -0,0 +1,197 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.minecraft; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.skcraft.launcher.util.Environment; +import com.skcraft.launcher.util.Platform; +import lombok.Data; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class Library { + + private String name; + private transient String group; + private transient String artifact; + private transient String version; + @JsonProperty("url") + private String baseUrl; + private Map natives; + private Extract extract; + private List rules; + + // Forge-added + private String comment; + + // Custom + private boolean locallyAvailable; + + public void setName(String name) { + this.name = name; + + if (name != null) { + String[] parts = name.split(":"); + this.group = parts[0]; + this.artifact = parts[1]; + this.version = parts[2]; + } else { + this.group = null; + this.artifact = null; + this.version = null; + } + } + + public boolean matches(Environment environment) { + boolean allow = false; + + if (getRules() != null) { + for (Rule rule : getRules()) { + if (rule.matches(environment)) { + allow = rule.getAction() == Action.ALLOW; + } + } + } else { + allow = true; + } + + return allow; + } + + @JsonIgnore + public String getGroup() { + return group; + } + + @JsonIgnore + public String getArtifact() { + return artifact; + } + + @JsonIgnore + public String getVersion() { + return version; + } + + public String getNativeString(Platform platform) { + if (getNatives() != null) { + switch (platform) { + case LINUX: + return getNatives().get("linux"); + case WINDOWS: + return getNatives().get("windows"); + case MAC_OS_X: + return getNatives().get("osx"); + default: + return null; + } + } else { + return null; + } + } + + public String getFilename(Environment environment) { + String nativeString = getNativeString(environment.getPlatform()); + if (nativeString != null) { + return String.format("%s-%s-%s.jar", + getArtifact(), getVersion(), nativeString); + } + + return String.format("%s-%s.jar", getArtifact(), getVersion()); + } + + public String getPath(Environment environment) { + StringBuilder builder = new StringBuilder(); + builder.append(getGroup().replace('.', '/')); + builder.append("/"); + builder.append(getArtifact()); + builder.append("/"); + builder.append(getVersion()); + builder.append("/"); + builder.append(getFilename(environment)); + String path = builder.toString(); + path = path.replace("${arch}", environment.getArchBits()); + return path; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Library library = (Library) o; + + if (name != null ? !name.equals(library.name) : library.name != null) + return false; + + return true; + } + + @Override + public int hashCode() { + return name != null ? name.hashCode() : 0; + } + + @Data + public static class Rule { + private Action action; + private OS os; + + public boolean matches(Environment environment) { + if (getOs() == null) { + return true; + } else { + return getOs().matches(environment); + } + } + } + + @Data + public static class OS { + private Platform platform; + private Pattern version; + + @JsonProperty("name") + @JsonDeserialize(using = PlatformDeserializer.class) + @JsonSerialize(using = PlatformSerializer.class) + public Platform getPlatform() { + return platform; + } + + public boolean matches(Environment environment) { + return (getPlatform() == null || getPlatform().equals(environment.getPlatform())) && + (getVersion() == null || getVersion().matcher(environment.getPlatformVersion()).matches()); + } + } + + @Data + public static class Extract { + private List exclude; + } + + private enum Action { + ALLOW, + DISALLOW; + + @JsonCreator + public static Action fromJson(String text) { + return valueOf(text.toUpperCase()); + } + + @JsonValue + public String toJson() { + return name().toLowerCase(); + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/PlatformDeserializer.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/PlatformDeserializer.java new file mode 100644 index 0000000..5eb8a51 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/PlatformDeserializer.java @@ -0,0 +1,35 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.minecraft; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.skcraft.launcher.util.Platform; + +import java.io.IOException; + +public class PlatformDeserializer extends JsonDeserializer { + + @Override + public Platform deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + String text = jsonParser.getText(); + if (text.equalsIgnoreCase("windows")) { + return Platform.WINDOWS; + } else if (text.equalsIgnoreCase("linux")) { + return Platform.LINUX; + } else if (text.equalsIgnoreCase("solaris")) { + return Platform.SOLARIS; + } else if (text.equalsIgnoreCase("osx")) { + return Platform.MAC_OS_X; + } else { + throw new IOException("Unknown platform: " + text); + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/PlatformSerializer.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/PlatformSerializer.java new file mode 100644 index 0000000..91a44b1 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/PlatformSerializer.java @@ -0,0 +1,41 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.minecraft; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.skcraft.launcher.util.Platform; + +import java.io.IOException; + +public class PlatformSerializer extends JsonSerializer { + + @Override + public void serialize(Platform platform, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) + throws IOException, JsonProcessingException { + switch (platform) { + case WINDOWS: + jsonGenerator.writeString("windows"); + break; + case MAC_OS_X: + jsonGenerator.writeString("osx"); + break; + case LINUX: + jsonGenerator.writeString("linux"); + break; + case SOLARIS: + jsonGenerator.writeString("solaris"); + break; + case UNKNOWN: + jsonGenerator.writeNull(); + break; + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/ReleaseList.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/ReleaseList.java new file mode 100644 index 0000000..4cfcc1d --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/ReleaseList.java @@ -0,0 +1,43 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.minecraft; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.NonNull; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ReleaseList { + + private LatestReleases latest; + private List versions; + + /** + * Get a release with the given ID. + * + * @param id the ID + * @return the release + */ + public Version find(@NonNull String id) { + for (Version version : getVersions()) { + if (version.getId().equals(id)) { + return version; + } + } + return null; + } + + @Data + public static class LatestReleases { + private String snapshot; + private String release; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Version.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Version.java new file mode 100644 index 0000000..c01f6cb --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/Version.java @@ -0,0 +1,57 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.minecraft; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Version { + + @Getter + @Setter + @NonNull + private String id; + + public Version() { + } + + public Version(@NonNull String id) { + this.id = id; + } + + @JsonIgnore + public String getName() { + return id; + } + + @Override + public String toString() { + return getName(); + } + + boolean thisEquals(Version other) { + return getId().equals(other.getId()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Version version = (Version) o; + return thisEquals(version) && version.thisEquals(this); + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java new file mode 100644 index 0000000..283bb8a --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java @@ -0,0 +1,36 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.minecraft; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.Date; +import java.util.LinkedHashSet; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class VersionManifest { + + private String id; + private Date time; + private Date releaseTime; + private String assets; + private String type; + private String processArguments; + private String minecraftArguments; + private String mainClass; + private int minimumLauncherVersion; + private LinkedHashSet libraries; + + @JsonIgnore + public String getAssetsIndex() { + return getAssets() != null ? getAssets() : "legacy"; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/BaseManifest.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/BaseManifest.java new file mode 100644 index 0000000..3aa84ea --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/BaseManifest.java @@ -0,0 +1,18 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import lombok.Data; + +@Data +public class BaseManifest { + + private String title; + private String name; + private String version; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/Condition.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/Condition.java new file mode 100644 index 0000000..8f0332c --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/Condition.java @@ -0,0 +1,21 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="if") +@JsonSubTypes({ + @JsonSubTypes.Type(value = RequireAny.class, name = "requireAny"), + @JsonSubTypes.Type(value = RequireAll.class, name = "requireAll") +}) +public interface Condition { + + boolean matches(); + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/Feature.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/Feature.java new file mode 100644 index 0000000..820e701 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/Feature.java @@ -0,0 +1,69 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import com.google.common.base.Strings; +import lombok.Data; + +@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="name") +@Data +public class Feature implements Comparable { + + public enum Recommendation { + STARRED, + AVOID; + + @JsonCreator + public static Recommendation fromJson(String text) { + return valueOf(text.toUpperCase()); + } + + @JsonValue + public String toJson() { + return name().toLowerCase(); + }; + }; + + private String name; + private String description; + private Recommendation recommendation; + private boolean selected; + + public Feature() { + } + + public Feature(String name, String description, boolean selected) { + this.name = name; + this.description = description; + this.selected = selected; + } + + public Feature(Feature feature) { + setName(feature.getName()); + setDescription(feature.getDescription()); + setSelected(feature.isSelected()); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object other) { + return super.equals(other); + } + + @Override + public int compareTo(Feature o) { + return Strings.nullToEmpty(getName()).compareTo(Strings.nullToEmpty(o.getName())); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/FileInstall.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/FileInstall.java new file mode 100644 index 0000000..3964787 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/FileInstall.java @@ -0,0 +1,95 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import com.google.common.io.Files; +import com.skcraft.launcher.install.InstallLog; +import com.skcraft.launcher.install.InstallLogFileMover; +import com.skcraft.launcher.install.Installer; +import com.skcraft.launcher.install.UpdateCache; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NonNull; +import org.apache.commons.io.FilenameUtils; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.skcraft.launcher.LauncherUtils.concat; + +@Data +@EqualsAndHashCode(callSuper = false) +public class FileInstall extends ManifestEntry { + + private static HashFunction hf = Hashing.sha1(); + private String version; + private String hash; + private String location; + private String to; + private long size; + private boolean userFile; + + @JsonIgnore + public String getImpliedVersion() { + return checkNotNull(version != null ? version : hash); + } + + @JsonIgnore + public String getTargetPath() { + return checkNotNull(this.to != null ? this.to : location); + } + + @Override + public void install(@NonNull Installer installer, @NonNull InstallLog log, + @NonNull UpdateCache cache, @NonNull File contentDir) throws IOException { + if (getWhen() != null && !getWhen().matches()) { + return; + } + + String targetPath = getTargetPath(); + File targetFile = new File(contentDir, targetPath); + String fileVersion = getImpliedVersion(); + URL url = concat(getManifest().getObjectsUrl(), getLocation()); + + if (shouldUpdate(cache, targetFile)) { + long size = this.size; + if (size <= 0) { + size = 10 * 1024; + } + + File tempFile = installer.getDownloader().download(url, fileVersion, size, to); + installer.queue(new InstallLogFileMover(log, tempFile, targetFile)); + } else { + log.add(to, to); + } + } + + private boolean shouldUpdate(UpdateCache cache, File targetFile) throws IOException { + if (targetFile.exists() && isUserFile()) { + return false; + } + + if (!targetFile.exists()) { + return true; + } + + if (hash != null) { + String existingHash = Files.hash(targetFile, hf).toString(); + if (existingHash.equalsIgnoreCase(hash)) { + return false; + } + } + + return cache.mark(FilenameUtils.normalize(getTargetPath()), getImpliedVersion()); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/LaunchModifier.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/LaunchModifier.java new file mode 100644 index 0000000..e8b961a --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/LaunchModifier.java @@ -0,0 +1,26 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import com.skcraft.launcher.launch.JavaProcessBuilder; +import lombok.Data; + +import java.util.List; + +@Data +public class LaunchModifier { + + private List flags; + + public void modify(JavaProcessBuilder builder) { + if (flags != null) { + for (String flag : flags) { + builder.getFlags().add(flag); + } + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/Manifest.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/Manifest.java new file mode 100644 index 0000000..cac0f7f --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/Manifest.java @@ -0,0 +1,94 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Strings; +import com.skcraft.launcher.Instance; +import com.skcraft.launcher.LauncherUtils; +import com.skcraft.launcher.model.minecraft.VersionManifest; +import com.skcraft.launcher.install.Installer; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Manifest extends BaseManifest { + + public static final int MIN_PROTOCOL_VERSION = 2; + + private int minimumVersion; + private URL baseUrl; + private String librariesLocation; + private String objectsLocation; + private String gameVersion; + @JsonProperty("launch") + private LaunchModifier launchModifier; + private List features = new ArrayList(); + @JsonManagedReference("manifest") + private List tasks = new ArrayList(); + @Getter @Setter @JsonIgnore + private Installer installer; + private VersionManifest versionManifest; + + @JsonIgnore + public URL getLibrariesUrl() { + if (Strings.nullToEmpty(getLibrariesLocation()) == null) { + return null; + } + + try { + return LauncherUtils.concat(baseUrl, Strings.nullToEmpty(getLibrariesLocation()) + "/"); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + @JsonIgnore + public URL getObjectsUrl() { + if (Strings.nullToEmpty(getObjectsLocation()) == null) { + return baseUrl; + } + + try { + return LauncherUtils.concat(baseUrl, Strings.nullToEmpty(getObjectsLocation()) + "/"); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + public void updateName(String name) { + if (name != null) { + setName(name); + } + } + + public void updateTitle(String title) { + if (title != null) { + setTitle(title); + } + } + + public void updateGameVersion(String gameVersion) { + if (gameVersion != null) { + setGameVersion(gameVersion); + } + } + + public void update(Instance instance) { + instance.setLaunchModifier(getLaunchModifier()); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestEntry.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestEntry.java new file mode 100644 index 0000000..8d06fad --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestEntry.java @@ -0,0 +1,38 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.skcraft.launcher.install.InstallLog; +import com.skcraft.launcher.install.Installer; +import com.skcraft.launcher.install.UpdateCache; +import lombok.Data; +import lombok.ToString; + +import java.io.File; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type", + defaultImpl = FileInstall.class) +@JsonSubTypes({ + @JsonSubTypes.Type(value = FileInstall.class, name = "file") +}) +@Data +@ToString(exclude = "manifest") +public abstract class ManifestEntry { + + @JsonBackReference("manifest") + private Manifest manifest; + private Condition when; + + public abstract void install(Installer installer, InstallLog log, UpdateCache cache, File contentDir) throws Exception; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestInfo.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestInfo.java new file mode 100644 index 0000000..e035579 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestInfo.java @@ -0,0 +1,19 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ManifestInfo extends BaseManifest { + + private String location; + private int priority; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/PackageList.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/PackageList.java new file mode 100644 index 0000000..8385ff3 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/PackageList.java @@ -0,0 +1,19 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import lombok.Data; + +import java.util.List; + +@Data +public class PackageList { + + private int minimumVersion; + private List packages; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/RequireAll.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/RequireAll.java new file mode 100644 index 0000000..ce84174 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/RequireAll.java @@ -0,0 +1,46 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Data +public class RequireAll implements Condition { + + private List features = new ArrayList(); + + public RequireAll() { + } + + public RequireAll(List features) { + this.features = features; + } + + public RequireAll(Feature... feature) { + features.addAll(Arrays.asList(feature)); + } + + @Override + public boolean matches() { + if (features == null) { + return true; + } + + for (Feature feature : features) { + if (!feature.isSelected()) { + return false; + } + } + + return true; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/RequireAny.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/RequireAny.java new file mode 100644 index 0000000..299595a --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/RequireAny.java @@ -0,0 +1,46 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.model.modpack; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Data +public class RequireAny implements Condition { + + private List features = new ArrayList(); + + public RequireAny() { + } + + public RequireAny(List features) { + this.features = features; + } + + public RequireAny(Feature... feature) { + features.addAll(Arrays.asList(feature)); + } + + @Override + public boolean matches() { + if (features == null) { + return true; + } + + for (Feature feature : features) { + if (feature.isSelected()) { + return true; + } + } + + return false; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/persistence/MkdirByteSink.java b/launcher/src/main/java/com/skcraft/launcher/persistence/MkdirByteSink.java new file mode 100644 index 0000000..e76be6c --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/persistence/MkdirByteSink.java @@ -0,0 +1,31 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.persistence; + +import com.google.common.io.ByteSink; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +class MkdirByteSink extends ByteSink { + + private final ByteSink delegate; + private final File dir; + + public MkdirByteSink(ByteSink delegate, File dir) { + this.delegate = delegate; + this.dir = dir; + } + + @Override + public OutputStream openStream() throws IOException { + dir.mkdirs(); + return delegate.openStream(); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/persistence/Persistence.java b/launcher/src/main/java/com/skcraft/launcher/persistence/Persistence.java new file mode 100644 index 0000000..c6580a8 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/persistence/Persistence.java @@ -0,0 +1,210 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.persistence; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.io.ByteSink; +import com.google.common.io.ByteSource; +import com.google.common.io.Closer; +import com.google.common.io.Files; +import lombok.NonNull; +import lombok.extern.java.Log; + +import java.io.*; +import java.util.WeakHashMap; +import java.util.logging.Level; + +/** + * Simple persistence framework that can read an object from a file, bind + * that object to that file, and allow any code having a reference to the + * object make changes to the object and save those changes back to disk. + *

+ * For example: + *
config = Persistence.load(file, Configuration.class);
+ * config.changeSomething();
+ * Persistence.commit(config);
+ */ +@Log +public final class Persistence { + + private static final ObjectMapper mapper = new ObjectMapper(); + private static final WeakHashMap bound = + new WeakHashMap(); + + private Persistence() { + } + + /** + * Bind an object to a path where the object will be saved. + * + * @param object the object + * @param sink the byte sink + */ + public static void bind(@NonNull Object object, @NonNull ByteSink sink) { + synchronized (bound) { + bound.put(object, sink); + } + } + + /** + * Save an object to file. + * + * @param object the object + * @throws java.io.IOException on save error + */ + public static void commit(@NonNull Object object) throws IOException { + ByteSink sink; + synchronized (bound) { + sink = bound.get(object); + if (sink == null) { + throw new IOException("Cannot persist unbound object: " + object); + } + } + + Closer closer = Closer.create(); + try { + OutputStream os = closer.register(sink.openBufferedStream()); + mapper.writeValue(os, object); + } finally { + closer.close(); + } + } + + /** + * Save an object to file, and send all errors to the log. + * + * @param object the object + */ + public static void commitAndForget(@NonNull Object object) { + try { + commit(object); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to save " + object.getClass() + ": " + object.toString(), e); + } + } + + /** + * Read an object from a byte source, without binding it. + * + * @param source byte source + * @param cls the class + * @param returnNull true to return null if the object could not be loaded + * @param the type of class + * @return an object + */ + public static V read(ByteSource source, Class cls, boolean returnNull) { + V object; + Closer closer = Closer.create(); + + try { + object = mapper.readValue(closer.register(source.openBufferedStream()), cls); + } catch (IOException e) { + if (!(e instanceof FileNotFoundException)) { + log.log(Level.INFO, "Failed to load" + cls.getCanonicalName(), e); + } + + if (returnNull) { + return null; + } + + try { + object = cls.newInstance(); + } catch (InstantiationException e1) { + throw new RuntimeException( + "Failed to construct object with no-arg constructor", e1); + } catch (IllegalAccessException e1) { + throw new RuntimeException( + "Failed to construct object with no-arg constructor", e1); + } + } finally { + try { + closer.close(); + } catch (IOException e) { + } + } + + return object; + } + + /** + * Read an object from file, without binding it. + * + * @param file the file + * @param cls the class + * @param returnNull true to return null if the object could not be loaded + * @param the type of class + * @return an object + */ + public static V read(File file, Class cls, boolean returnNull) { + return read(Files.asByteSource(file), cls, returnNull); + } + + + /** + * Read an object from file, without binding it. + * + * @param file the file + * @param cls the class + * @param the type of class + * @return an object + */ + public static V read(File file, Class cls) { + return read(file, cls, false); + } + + /** + * Read an object from file. + * + * @param file the file + * @param cls the class + * @param returnNull true to return null if the object could not be loaded + * @param the type of class + * @return an object + */ + public static V load(File file, Class cls, boolean returnNull) { + ByteSource source = Files.asByteSource(file); + ByteSink sink = new MkdirByteSink(Files.asByteSink(file), file.getParentFile()); + + Scrambled scrambled = cls.getAnnotation(Scrambled.class); + if (cls.getAnnotation(Scrambled.class) != null) { + source = new ScramblingSourceFilter(source, scrambled.value()); + sink = new ScramblingSinkFilter(sink, scrambled.value()); + } + + V object = read(source, cls, returnNull); + Persistence.bind(object, sink); + return object; + } + + /** + * Read an object from file. + * + *

If the file does not exist or loading fails, construct a new instance of + * the given class by using its no-arg constructor.

+ * + * @param file the file + * @param cls the class + * @param the type of class + * @return an object + */ + public static V load(File file, Class cls) { + return load(file, cls, false); + } + + /** + * Write an object to file. + * + * @param file the file + * @param object the object + * @throws java.io.IOException on I/O error + */ + public static void write(File file, Object object) throws IOException { + file.getParentFile().mkdirs(); + mapper.writeValue(file, object); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/persistence/Scrambled.java b/launcher/src/main/java/com/skcraft/launcher/persistence/Scrambled.java new file mode 100644 index 0000000..672bbb0 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/persistence/Scrambled.java @@ -0,0 +1,47 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.persistence; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Classes that are annotated with this will be saved scrambled + * to disk when saved using {@link com.skcraft.launcher.persistence.Persistence}. + *

+ * The data may be scrambled using an encryption algorithm, but it's not + * done with security in mind. Decryption requires a key, and that + * key would either have to be stored in the source code, defeating the + * purpose of encryption, or the user would have to be prompted with a + * password every time (possibly through an OS key ring service). + * That creates extra hassle, so it is not done here. + *

+ * Therefore , you should not depend on data that is scrambled to + * be secure. It does, however, make it impossible for most people + * to just read the file's contents, which is "better than nothing" + * Documentation should not indicate to the user that the data is + * protected however, because that would provide a false sense of + * security. + *

+ * Account data is scrambled to make it harder to extract passwords. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Scrambled { + + /** + * A key used in scrambling. + *

+ * The key should not change once deployed. + * + * @return a key + */ + String value(); + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/persistence/ScramblingSinkFilter.java b/launcher/src/main/java/com/skcraft/launcher/persistence/ScramblingSinkFilter.java new file mode 100644 index 0000000..862a6e9 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/persistence/ScramblingSinkFilter.java @@ -0,0 +1,58 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.persistence; + +import com.google.common.io.ByteSink; + +import javax.crypto.*; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import java.io.IOException; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Random; + +class ScramblingSinkFilter extends ByteSink { + + private final ByteSink delegate; + private final String key; + + public ScramblingSinkFilter(ByteSink delegate, String key) { + this.delegate = delegate; + this.key = key; + } + + @Override + public OutputStream openStream() throws IOException { + Cipher cipher = null; + try { + cipher = getCipher(Cipher.ENCRYPT_MODE, key); + } catch (Throwable e) { + throw new IOException("Failed to create cipher", e); + } + return new CipherOutputStream(delegate.openStream(), cipher); + } + + public static Cipher getCipher(int mode, String password) + throws InvalidKeySpecException, NoSuchAlgorithmException, + NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException { + // These parameters were used for encrypting lastlogin on old official Minecraft launchers + Random random = new Random(0x29482c2L); + byte salt[] = new byte[8]; + random.nextBytes(salt); + PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 5); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); + SecretKey key = factory.generateSecret(new PBEKeySpec(password.toCharArray())); + Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES"); + cipher.init(mode, key, paramSpec); + return cipher; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/persistence/ScramblingSourceFilter.java b/launcher/src/main/java/com/skcraft/launcher/persistence/ScramblingSourceFilter.java new file mode 100644 index 0000000..fdfa03b --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/persistence/ScramblingSourceFilter.java @@ -0,0 +1,37 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.persistence; + +import com.google.common.io.ByteSource; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import java.io.IOException; +import java.io.InputStream; + +class ScramblingSourceFilter extends ByteSource { + + private final ByteSource delegate; + private final String key; + + public ScramblingSourceFilter(ByteSource delegate, String key) { + this.delegate = delegate; + this.key = key; + } + + @Override + public InputStream openStream() throws IOException { + Cipher cipher = null; + try { + cipher = ScramblingSinkFilter.getCipher(Cipher.DECRYPT_MODE, key); + } catch (Throwable e) { + throw new IOException("Failed to create cipher", e); + } + return new CipherInputStream(delegate.openStream(), cipher); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/selfupdate/ComparableVersion.java b/launcher/src/main/java/com/skcraft/launcher/selfupdate/ComparableVersion.java new file mode 100644 index 0000000..1adcf30 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/selfupdate/ComparableVersion.java @@ -0,0 +1,366 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.skcraft.launcher.selfupdate; + +import java.util.*; + +/** + * Generic implementation of version comparison. + *

+ * NOTE: This class is a copy of r658725 of http://svn.apache.org/repos/asf/maven/artifact/trunk/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java. + * + * @author Kenney Westerhof + * @author Herve Boutemy + * @version $Id$ + */ +@SuppressWarnings("unchecked") +public class ComparableVersion + implements Comparable { + private String value; + + private String canonical; + + private ListItem items; + + private interface Item { + public static final int INTEGER_ITEM = 0; + public static final int STRING_ITEM = 1; + public static final int LIST_ITEM = 2; + + public int compareTo(Item item); + + public int getType(); + + public boolean isNull(); + } + + /** + * Represents a numeric item in the version item list. + */ + private static class IntegerItem + implements Item { + private Integer value; + + public IntegerItem(Integer i) { + this.value = i; + } + + public int getType() { + return INTEGER_ITEM; + } + + public boolean isNull() { + return (value == 0); + } + + public int compareTo(Item item) { + if (item == null) { + return value == 0 ? 0 : 1; // 1.0 == 1, 1.1 > 1 + } + + switch (item.getType()) { + case INTEGER_ITEM: + return value.compareTo(((IntegerItem) item).value); + + case STRING_ITEM: + return 1; // 1.1 > 1-sp + + case LIST_ITEM: + return 1; // 1.1 > 1-1 + + default: + throw new RuntimeException("invalid item: " + item.getClass()); + } + } + + public String toString() { + return value.toString(); + } + } + + /** + * Represents a string in the version item list, usually a qualifier. + */ + private static class StringItem + implements Item { + private final static String[] QUALIFIERS = {"snapshot", "alpha", "beta", "milestone", "rc", "", "sp"}; + + private final static List _QUALIFIERS = Arrays.asList(QUALIFIERS); + + private final static Properties ALIASES = new Properties(); + + static { + ALIASES.put("ga", ""); + ALIASES.put("final", ""); + ALIASES.put("cr", "rc"); + } + + /** + * A comparable for the empty-string qualifier. This one is used to determine if a given qualifier makes the + * version older than one without a qualifier, or more recent. + */ + private static Comparable RELEASE_VERSION_INDEX = String.valueOf(_QUALIFIERS.indexOf("")); + + private String value; + + public StringItem(String value, boolean followedByDigit) { + if (followedByDigit && value.length() == 1) { + // a1 = alpha-1, b1 = beta-1, m1 = milestone-1 + switch (value.charAt(0)) { + case 'a': + value = "alpha"; + break; + case 'b': + value = "beta"; + break; + case 'm': + value = "milestone"; + break; + } + } + this.value = ALIASES.getProperty(value, value); + } + + public int getType() { + return STRING_ITEM; + } + + public boolean isNull() { + return (comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX) == 0); + } + + /** + * Returns a comparable for a qualifier. + *

+ * This method both takes into account the ordering of known qualifiers as well as lexical ordering for unknown + * qualifiers. + *

+ * just returning an Integer with the index here is faster, but requires a lot of if/then/else to check for -1 + * or QUALIFIERS.size and then resort to lexical ordering. Most comparisons are decided by the first character, + * so this is still fast. If more characters are needed then it requires a lexical sort anyway. + * + * @param qualifier + * @return + */ + public static Comparable comparableQualifier(String qualifier) { + int i = _QUALIFIERS.indexOf(qualifier); + + return i == -1 ? _QUALIFIERS.size() + "-" + qualifier : String.valueOf(i); + } + + public int compareTo(Item item) { + if (item == null) { + // 1-rc < 1, 1-ga > 1 + return comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX); + } + switch (item.getType()) { + case INTEGER_ITEM: + return -1; // 1.any < 1.1 ? + + case STRING_ITEM: + return comparableQualifier(value).compareTo(comparableQualifier(((StringItem) item).value)); + + case LIST_ITEM: + return -1; // 1.any < 1-1 + + default: + throw new RuntimeException("invalid item: " + item.getClass()); + } + } + + public String toString() { + return value; + } + } + + /** + * Represents a version list item. This class is used both for the global item list and for sub-lists (which start + * with '-(number)' in the version specification). + */ + private static class ListItem + extends ArrayList + implements Item { + public int getType() { + return LIST_ITEM; + } + + public boolean isNull() { + return (size() == 0); + } + + void normalize() { + for (ListIterator iterator = listIterator(size()); iterator.hasPrevious(); ) { + Item item = (Item) iterator.previous(); + if (item.isNull()) { + iterator.remove(); // remove null trailing items: 0, "", empty list + } else { + break; + } + } + } + + public int compareTo(Item item) { + if (item == null) { + if (size() == 0) { + return 0; // 1-0 = 1- (normalize) = 1 + } + Item first = (Item) get(0); + return first.compareTo(null); + } + switch (item.getType()) { + case INTEGER_ITEM: + return -1; // 1-1 < 1.0.x + + case STRING_ITEM: + return 1; // 1-1 > 1-sp + + case LIST_ITEM: + Iterator left = iterator(); + Iterator right = ((ListItem) item).iterator(); + + while (left.hasNext() || right.hasNext()) { + Item l = left.hasNext() ? (Item) left.next() : null; + Item r = right.hasNext() ? (Item) right.next() : null; + + // if this is shorter, then invert the compare and mul with -1 + int result = l == null ? -1 * r.compareTo(l) : l.compareTo(r); + + if (result != 0) { + return result; + } + } + + return 0; + + default: + throw new RuntimeException("invalid item: " + item.getClass()); + } + } + + public String toString() { + StringBuffer buffer = new StringBuffer("("); + for (Iterator iter = iterator(); iter.hasNext(); ) { + buffer.append(iter.next()); + if (iter.hasNext()) { + buffer.append(','); + } + } + buffer.append(')'); + return buffer.toString(); + } + } + + public ComparableVersion(String version) { + parseVersion(version); + } + + public final void parseVersion(String version) { + this.value = version; + + items = new ListItem(); + + version = version.toLowerCase(Locale.ENGLISH); + + ListItem list = items; + + Stack stack = new Stack(); + stack.push(list); + + boolean isDigit = false; + + int startIndex = 0; + + for (int i = 0; i < version.length(); i++) { + char c = version.charAt(i); + + if (c == '.') { + if (i == startIndex) { + list.add(new IntegerItem(0)); + } else { + list.add(parseItem(isDigit, version.substring(startIndex, i))); + } + startIndex = i + 1; + } else if (c == '-') { + if (i == startIndex) { + list.add(new IntegerItem(0)); + } else { + list.add(parseItem(isDigit, version.substring(startIndex, i))); + } + startIndex = i + 1; + + if (isDigit) { + list.normalize(); // 1.0-* = 1-* + + if ((i + 1 < version.length()) && Character.isDigit(version.charAt(i + 1))) { + // new ListItem only if previous were digits and new char is a digit, + // ie need to differentiate only 1.1 from 1-1 + list.add(list = new ListItem()); + + stack.push(list); + } + } + } else if (Character.isDigit(c)) { + if (!isDigit && i > startIndex) { + list.add(new StringItem(version.substring(startIndex, i), true)); + startIndex = i; + } + + isDigit = true; + } else { + if (isDigit && i > startIndex) { + list.add(parseItem(true, version.substring(startIndex, i))); + startIndex = i; + } + + isDigit = false; + } + } + + if (version.length() > startIndex) { + list.add(parseItem(isDigit, version.substring(startIndex))); + } + + while (!stack.isEmpty()) { + list = (ListItem) stack.pop(); + list.normalize(); + } + + canonical = items.toString(); + } + + private static Item parseItem(boolean isDigit, String buf) { + return isDigit ? new IntegerItem(new Integer(buf)) : new StringItem(buf, false); + } + + public int compareTo(Object o) { + return items.compareTo(((ComparableVersion) o).items); + } + + public String toString() { + return value; + } + + public boolean equals(Object o) { + return (o instanceof ComparableVersion) && canonical.equals(((ComparableVersion) o).canonical); + } + + public int hashCode() { + return canonical.hashCode(); + } +} \ No newline at end of file diff --git a/launcher/src/main/java/com/skcraft/launcher/selfupdate/LatestVersionInfo.java b/launcher/src/main/java/com/skcraft/launcher/selfupdate/LatestVersionInfo.java new file mode 100644 index 0000000..b299a3c --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/selfupdate/LatestVersionInfo.java @@ -0,0 +1,19 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.selfupdate; + +import lombok.Data; + +import java.net.URL; + +@Data +public class LatestVersionInfo { + + private String version; + private URL url; + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/selfupdate/SelfUpdater.java b/launcher/src/main/java/com/skcraft/launcher/selfupdate/SelfUpdater.java new file mode 100644 index 0000000..92af33c --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/selfupdate/SelfUpdater.java @@ -0,0 +1,74 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.selfupdate; + +import com.skcraft.concurrency.DefaultProgress; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.install.FileMover; +import com.skcraft.launcher.install.Installer; +import lombok.NonNull; + +import java.io.File; +import java.net.URL; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static com.skcraft.launcher.util.SharedLocale._; + +public class SelfUpdater implements Callable, ProgressObservable { + + public static boolean updatedAlready = false; + + private final Launcher launcher; + private final URL url; + private final Installer installer; + private ProgressObservable progress = new DefaultProgress(0, _("updater.updating")); + + public SelfUpdater(@NonNull Launcher launcher, @NonNull URL url) { + this.launcher = launcher; + this.url = url; + this.installer = new Installer(launcher.getInstallerDir()); + } + + @Override + public File call() throws Exception { + ExecutorService executor = Executors.newSingleThreadExecutor(); + + try { + File dir = launcher.getLauncherBinariesDir(); + File file = new File(dir, String.valueOf(System.currentTimeMillis()) + ".jar.pack"); + File tempFile = installer.getDownloader().download(url, "", 10000, "launcher.jar.pack"); + + progress = installer.getDownloader(); + installer.download(); + + installer.queue(new FileMover(tempFile, file)); + + progress = installer; + installer.execute(); + + updatedAlready = true; + + return file; + } finally { + executor.shutdownNow(); + } + } + + @Override + public double getProgress() { + return progress.getProgress(); + } + + @Override + public String getStatus() { + return progress.getStatus(); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/selfupdate/UpdateChecker.java b/launcher/src/main/java/com/skcraft/launcher/selfupdate/UpdateChecker.java new file mode 100644 index 0000000..f0e8517 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/selfupdate/UpdateChecker.java @@ -0,0 +1,63 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.selfupdate; + +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.LauncherException; +import com.skcraft.launcher.util.HttpRequest; +import lombok.NonNull; +import lombok.extern.java.Log; + +import java.net.URL; +import java.util.concurrent.Callable; + +import static com.skcraft.launcher.util.SharedLocale._; + +/** + * A worker that checks for an update to the launcher. A URL is returned + * if there is an update to be downloaded. + */ +@Log +public class UpdateChecker implements Callable { + + private final Launcher launcher; + + public UpdateChecker(@NonNull Launcher launcher) { + this.launcher = launcher; + } + + @Override + public URL call() throws Exception { + try { + UpdateChecker.log.info("Checking for update..."); + + URL url = HttpRequest.url(launcher.getProperties().getProperty("selfUpdateUrl")); + + LatestVersionInfo versionInfo = HttpRequest.get(url) + .execute() + .expectResponseCode(200) + .returnContent() + .asJson(LatestVersionInfo.class); + + ComparableVersion current = new ComparableVersion(launcher.getVersion()); + ComparableVersion latest = new ComparableVersion(versionInfo.getVersion()); + + UpdateChecker.log.info("Latest version is " + latest + ", while current is " + current); + + if (latest.compareTo(current) >= 1) { + UpdateChecker.log.info("Update available at " + versionInfo.getUrl()); + return versionInfo.getUrl(); + } else { + UpdateChecker.log.info("No update required."); + return null; + } + } catch (Exception e) { + throw new LauncherException(e, _("errors.selfUpdateCheckError")); + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/ActionListeners.java b/launcher/src/main/java/com/skcraft/launcher/swing/ActionListeners.java new file mode 100644 index 0000000..807d3ae --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/ActionListeners.java @@ -0,0 +1,53 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +/** + * Utility method to make {@link ActionListeners}. + */ +public final class ActionListeners { + + private ActionListeners() { + } + + public static ActionListener dispose(final Window window) { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + window.dispose(); + } + }; + } + + public static ActionListener openURL(final Component component, final String url) { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SwingHelper.openURL(url, component); + } + }; + } + + public static ActionListener browseDir( + final Component component, final File dir, final boolean create) { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (create) { + dir.mkdirs(); + } + SwingHelper.browseDir(dir, component); + } + }; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/CheckboxTable.java b/launcher/src/main/java/com/skcraft/launcher/swing/CheckboxTable.java new file mode 100644 index 0000000..797ff05 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/CheckboxTable.java @@ -0,0 +1,31 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import javax.swing.*; +import javax.swing.table.TableModel; +import java.awt.*; + +public class CheckboxTable extends JTable { + + public CheckboxTable() { + setShowGrid(false); + setRowHeight((int) (Math.max(getRowHeight(), new JCheckBox().getPreferredSize().getHeight() - 2))); + setIntercellSpacing(new Dimension(0, 0)); + setFillsViewportHeight(true); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + + @Override + public void setModel(TableModel dataModel) { + super.setModel(dataModel); + try { + getColumnModel().getColumn(0).setMaxWidth((int) new JCheckBox().getPreferredSize().getWidth()); + } catch (ArrayIndexOutOfBoundsException e) { + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/DoubleClickToButtonAdapter.java b/launcher/src/main/java/com/skcraft/launcher/swing/DoubleClickToButtonAdapter.java new file mode 100644 index 0000000..5c6a841 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/DoubleClickToButtonAdapter.java @@ -0,0 +1,28 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import javax.swing.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class DoubleClickToButtonAdapter extends MouseAdapter { + + private final AbstractButton button; + + public DoubleClickToButtonAdapter(AbstractButton button) { + this.button = button; + } + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + button.doClick(); + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/FeatureTableModel.java b/launcher/src/main/java/com/skcraft/launcher/swing/FeatureTableModel.java new file mode 100644 index 0000000..8e4b873 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/FeatureTableModel.java @@ -0,0 +1,107 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import com.skcraft.launcher.model.modpack.Feature; + +import javax.swing.table.AbstractTableModel; +import java.util.List; + +import static com.skcraft.launcher.util.SharedLocale._; + +public class FeatureTableModel extends AbstractTableModel { + + private final List features; + + public FeatureTableModel(List features) { + this.features = features; + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 1: + return _("features.nameColumn"); + default: + return null; + } + } + + @Override + public Class getColumnClass(int columnIndex) { + switch (columnIndex) { + case 0: + return Boolean.class; + case 1: + return String.class; + default: + return null; + } + } + + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + features.get(rowIndex).setSelected((boolean) (Boolean) value); + break; + case 1: + default: + break; + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + return true; + case 1: + return false; + default: + return false; + } + } + + @Override + public int getRowCount() { + return features.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + return features.get(rowIndex).isSelected(); + case 1: + Feature feature = features.get(rowIndex); + return "" + SwingHelper.htmlEscape(feature.getName()) + getAddendum(feature) + ""; + default: + return null; + } + } + + private String getAddendum(Feature feature) { + if (feature.getRecommendation() == null) { + return ""; + } + switch (feature.getRecommendation()) { + case STARRED: + return " " + _("features.starred") + ""; + case AVOID: + return " " + _("features.avoid") + ""; + default: + return ""; + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/FormPanel.java b/launcher/src/main/java/com/skcraft/launcher/swing/FormPanel.java new file mode 100644 index 0000000..5808227 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/FormPanel.java @@ -0,0 +1,53 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import javax.swing.*; +import java.awt.*; + +public class FormPanel extends JPanel { + + private static final GridBagConstraints labelConstraints; + private static final GridBagConstraints fieldConstraints; + private static final GridBagConstraints wideFieldConstraints; + + private final GridBagLayout layout; + + static { + fieldConstraints = new GridBagConstraints(); + fieldConstraints.fill = GridBagConstraints.HORIZONTAL; + fieldConstraints.weightx = 1.0; + fieldConstraints.gridwidth = GridBagConstraints.REMAINDER; + fieldConstraints.insets = new Insets(5, 5, 2, 5); + + labelConstraints = (GridBagConstraints) fieldConstraints.clone(); + labelConstraints.weightx = 0.0; + labelConstraints.gridwidth = 1; + labelConstraints.insets = new Insets(4, 5, 1, 10); + + wideFieldConstraints = (GridBagConstraints) fieldConstraints.clone(); + wideFieldConstraints.insets = new Insets(7, 2, 1, 2); + } + + public FormPanel() { + setLayout(layout = new GridBagLayout()); + setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + } + + public void addRow(Component label, Component component) { + add(label); + add(component); + layout.setConstraints(label, labelConstraints); + layout.setConstraints(component, fieldConstraints); + } + + public void addRow(Component component) { + add(component); + layout.setConstraints(component, wideFieldConstraints); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/HeaderPanel.java b/launcher/src/main/java/com/skcraft/launcher/swing/HeaderPanel.java new file mode 100644 index 0000000..da04fa3 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/HeaderPanel.java @@ -0,0 +1,27 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import javax.swing.*; +import java.awt.*; + +public class HeaderPanel extends JPanel { + + public HeaderPanel() { + setBackground(new Color(0xDB5036)); + } + + @Override + public Dimension getPreferredSize() { + return new Dimension(200, 60); + } + + @Override + public void paint(Graphics g) { + super.paint(g); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/InstanceTable.java b/launcher/src/main/java/com/skcraft/launcher/swing/InstanceTable.java new file mode 100644 index 0000000..85fdce0 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/InstanceTable.java @@ -0,0 +1,31 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import javax.swing.*; +import javax.swing.table.TableModel; +import java.awt.*; + +public class InstanceTable extends JTable { + + public InstanceTable() { + setShowGrid(false); + setRowHeight(Math.max(getRowHeight() + 4, 20)); + setIntercellSpacing(new Dimension(0, 0)); + setFillsViewportHeight(true); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + + @Override + public void setModel(TableModel dataModel) { + super.setModel(dataModel); + try { + getColumnModel().getColumn(0).setMaxWidth(24); + } catch (ArrayIndexOutOfBoundsException e) { + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/InstanceTableModel.java b/launcher/src/main/java/com/skcraft/launcher/swing/InstanceTableModel.java new file mode 100644 index 0000000..03887bf --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/InstanceTableModel.java @@ -0,0 +1,132 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import com.skcraft.launcher.Instance; +import com.skcraft.launcher.InstanceList; +import com.skcraft.launcher.Launcher; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import java.awt.*; + +import static com.skcraft.launcher.util.SharedLocale._; + +public class InstanceTableModel extends AbstractTableModel { + + private final InstanceList instances; + private final ImageIcon instanceIcon; + private final ImageIcon customInstanceIcon; + private final ImageIcon downloadIcon; + + public InstanceTableModel(InstanceList instances) { + this.instances = instances; + instanceIcon = new ImageIcon(SwingHelper.readIconImage(Launcher.class, "instance_icon.png") + .getScaledInstance(16, 16, Image.SCALE_SMOOTH)); + customInstanceIcon = new ImageIcon(SwingHelper.readIconImage(Launcher.class, "custom_instance_icon.png") + .getScaledInstance(16, 16, Image.SCALE_SMOOTH)); + downloadIcon = new ImageIcon(SwingHelper.readIconImage(Launcher.class, "download_icon.png") + .getScaledInstance(14, 14, Image.SCALE_SMOOTH)); + } + + public void update() { + instances.sort(); + fireTableDataChanged(); + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: + return ""; + case 1: + return _("launcher.modpackColumn"); + default: + return null; + } + } + + @Override + public Class getColumnClass(int columnIndex) { + switch (columnIndex) { + case 0: + return ImageIcon.class; + case 1: + return String.class; + default: + return null; + } + } + + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + instances.get(rowIndex).setSelected((boolean) (Boolean) value); + break; + case 1: + default: + break; + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + return true; + case 1: + return false; + default: + return false; + } + } + + @Override + public int getRowCount() { + return instances.size(); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + Instance instance; + switch (columnIndex) { + case 0: + instance = instances.get(rowIndex); + if (!instance.isLocal()) { + return downloadIcon; + } else if (instance.getManifestURL() != null) { + return instanceIcon; + } else { + return customInstanceIcon; + } + case 1: + instance = instances.get(rowIndex); + return "" + SwingHelper.htmlEscape(instance.getTitle()) + getAddendum(instance) + ""; + default: + return null; + } + } + + private String getAddendum(Instance instance) { + if (!instance.isLocal()) { + return " " + _("launcher.notInstalledHint") + ""; + } else if (!instance.isInstalled()) { + return " " + _("launcher.requiresUpdateHint") + ""; + } else if (instance.isUpdatePending()) { + return " " + _("launcher.updatePendingHint") + ""; + } else { + return ""; + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/LinedBoxPanel.java b/launcher/src/main/java/com/skcraft/launcher/swing/LinedBoxPanel.java new file mode 100644 index 0000000..cb61e11 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/LinedBoxPanel.java @@ -0,0 +1,52 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import lombok.Getter; +import lombok.Setter; + +import javax.swing.*; +import java.awt.*; + +public class LinedBoxPanel extends JPanel { + + @Getter + private final boolean horizontal; + @Getter @Setter + private int spacing = 6; + private boolean needsSpacer = false; + + public LinedBoxPanel(boolean horizontal) { + this.horizontal = horizontal; + setLayout(new BoxLayout(this, + horizontal ? BoxLayout.X_AXIS : BoxLayout.Y_AXIS)); + setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + } + + public LinedBoxPanel fullyPadded() { + setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + return this; + } + + public void addElement(Component component) { + if (needsSpacer) { + add(horizontal ? + Box.createHorizontalStrut(spacing) : + Box.createVerticalStrut(spacing)); + } + add(component); + needsSpacer = true; + } + + public void addGlue() { + add(horizontal ? + Box.createHorizontalGlue() : + Box.createVerticalGlue()); + needsSpacer = false; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/LinkButton.java b/launcher/src/main/java/com/skcraft/launcher/swing/LinkButton.java new file mode 100644 index 0000000..30c45fb --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/LinkButton.java @@ -0,0 +1,71 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class LinkButton extends JButton { + + private static final Color LINK_COLOR = Color.blue; + private static final Border LINK_BORDER = BorderFactory.createEmptyBorder(0, 0, 1, 0); + private static final Border HOVER_BORDER = BorderFactory.createMatteBorder(0, 0, 1, 0, LINK_COLOR); + + public LinkButton() { + super(); + setupLink(); + } + + public LinkButton(Action a) { + super(a); + setupLink(); + } + + public LinkButton(Icon icon) { + super(icon); + setupLink(); + } + + public LinkButton(String text, Icon icon) { + super(text, icon); + setupLink(); + } + + public LinkButton(String text) { + super(text); + setupLink(); + } + + public void setupLink() { + setBorder(LINK_BORDER); + setForeground(LINK_COLOR); + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + setFocusPainted(false); + setRequestFocusEnabled(false); + setContentAreaFilled(false); + addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + ((JComponent) e.getComponent()).setBorder(HOVER_BORDER); + } + + @Override + public void mouseReleased(MouseEvent e) { + ((JComponent) e.getComponent()).setBorder(LINK_BORDER); + } + + @Override + public void mouseExited(MouseEvent e) { + ((JComponent) e.getComponent()).setBorder(LINK_BORDER); + } + }); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/MessageLog.java b/launcher/src/main/java/com/skcraft/launcher/swing/MessageLog.java new file mode 100644 index 0000000..ccc877a --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/MessageLog.java @@ -0,0 +1,313 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import com.skcraft.launcher.LauncherUtils; +import com.skcraft.launcher.util.LimitLinesDocumentListener; +import com.skcraft.launcher.util.SimpleLogFormatter; + +import javax.swing.*; +import javax.swing.text.*; +import java.awt.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import static org.apache.commons.io.IOUtils.closeQuietly; + +/** + * A simple message log. + */ +public class MessageLog extends JPanel { + + private static final Logger rootLogger = Logger.getLogger(""); + + private final int numLines; + private final boolean colorEnabled; + + protected JTextComponent textComponent; + protected Document document; + + private Handler loggerHandler; + protected final SimpleAttributeSet defaultAttributes = new SimpleAttributeSet(); + protected final SimpleAttributeSet highlightedAttributes; + protected final SimpleAttributeSet errorAttributes; + protected final SimpleAttributeSet infoAttributes; + protected final SimpleAttributeSet debugAttributes; + + public MessageLog(int numLines, boolean colorEnabled) { + this.numLines = numLines; + this.colorEnabled = colorEnabled; + + this.highlightedAttributes = new SimpleAttributeSet(); + StyleConstants.setForeground(highlightedAttributes, new Color(0xFF7F00)); + + this.errorAttributes = new SimpleAttributeSet(); + StyleConstants.setForeground(errorAttributes, new Color(0xFF0000)); + this.infoAttributes = new SimpleAttributeSet(); + this.debugAttributes = new SimpleAttributeSet(); + + setLayout(new BorderLayout()); + + initComponents(); + } + + private void initComponents() { + if (colorEnabled) { + JTextPane text = new JTextPane() { + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + }; + this.textComponent = text; + } else { + JTextArea text = new JTextArea(); + this.textComponent = text; + text.setLineWrap(true); + text.setWrapStyleWord(true); + } + + textComponent.setFont(new JLabel().getFont()); + textComponent.setEditable(false); + textComponent.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + DefaultCaret caret = (DefaultCaret) textComponent.getCaret(); + caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); + document = textComponent.getDocument(); + document.addDocumentListener(new LimitLinesDocumentListener(numLines, true)); + + JScrollPane scrollText = new JScrollPane(textComponent); + scrollText.setBorder(null); + scrollText.setVerticalScrollBarPolicy( + ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + scrollText.setHorizontalScrollBarPolicy( + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + + add(scrollText, BorderLayout.CENTER); + } + + public String getPastableText() { + String text = textComponent.getText().replaceAll("[\r\n]+", "\n"); + text = text.replaceAll("Session ID is [A-Fa-f0-9]+", "Session ID is [redacted]"); + return text; + } + + public void clear() { + textComponent.setText(""); + } + + /** + * Log a message given the {@link javax.swing.text.AttributeSet}. + * + * @param line line + * @param attributes attribute set, or null for none + */ + public void log(String line, AttributeSet attributes) { + if (colorEnabled) { + if (line.startsWith("(!!)")) { + attributes = highlightedAttributes; + } + } + + try { + int offset = document.getLength(); + document.insertString(offset, line, + (attributes != null && colorEnabled) ? attributes : defaultAttributes); + textComponent.setCaretPosition(document.getLength()); + } catch (BadLocationException ble) { + + } + } + + /** + * Get an output stream that can be written to. + * + * @return output stream + */ + public ConsoleOutputStream getOutputStream() { + return getOutputStream((AttributeSet) null); + } + + /** + * Get an output stream with the given attribute set. + * + * @param attributes attributes + * @return output stream + */ + public ConsoleOutputStream getOutputStream(AttributeSet attributes) { + return new ConsoleOutputStream(attributes); + } + + /** + * Get an output stream using the give color. + * + * @param color color to use + * @return output stream + */ + public ConsoleOutputStream getOutputStream(Color color) { + SimpleAttributeSet attributes = new SimpleAttributeSet(); + StyleConstants.setForeground(attributes, color); + return getOutputStream(attributes); + } + + /** + * Consume an input stream and print it to the dialog. The consumer + * will be in a separate daemon thread. + * + * @param from stream to read + */ + public void consume(InputStream from) { + consume(from, getOutputStream()); + } + + /** + * Consume an input stream and print it to the dialog. The consumer + * will be in a separate daemon thread. + * + * @param from stream to read + * @param color color to use + */ + public void consume(InputStream from, Color color) { + consume(from, getOutputStream(color)); + } + + /** + * Consume an input stream and print it to the dialog. The consumer + * will be in a separate daemon thread. + * + * @param from stream to read + * @param attributes attributes + */ + public void consume(InputStream from, AttributeSet attributes) { + consume(from, getOutputStream(attributes)); + } + + /** + * Internal method to consume a stream. + * + * @param from stream to consume + * @param outputStream console stream to write to + */ + private void consume(InputStream from, ConsoleOutputStream outputStream) { + final InputStream in = from; + final PrintWriter out = new PrintWriter(outputStream, true); + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + byte[] buffer = new byte[1024]; + try { + int len; + while ((len = in.read(buffer)) != -1) { + String s = new String(buffer, 0, len); + System.out.print(s); + out.append(s); + out.flush(); + } + } catch (IOException e) { + } finally { + closeQuietly(in); + closeQuietly(out); + } + } + }); + thread.setDaemon(true); + thread.start(); + } + + /** + * Register a global logger listener. + */ + public void registerLoggerHandler() { + loggerHandler = new ConsoleLoggerHandler(); + rootLogger.addHandler(loggerHandler); + } + + /** + * Detach the handler on the global logger. + */ + public void detachGlobalHandler() { + if (loggerHandler != null) { + rootLogger.removeHandler(loggerHandler); + loggerHandler = null; + } + } + + public SimpleAttributeSet asDefault() { + return defaultAttributes; + } + + public SimpleAttributeSet asHighlighted() { + return highlightedAttributes; + } + + public SimpleAttributeSet asError() { + return errorAttributes; + } + + public SimpleAttributeSet asInfo() { + return infoAttributes; + } + + public SimpleAttributeSet asDebug() { + return debugAttributes; + } + + /** + * Used to send logger messages to the console. + */ + private class ConsoleLoggerHandler extends Handler { + private final SimpleLogFormatter formatter = new SimpleLogFormatter(); + + @Override + public void publish(LogRecord record) { + Level level = record.getLevel(); + Throwable t = record.getThrown(); + AttributeSet attributes = defaultAttributes; + + if (level.intValue() >= Level.WARNING.intValue()) { + attributes = errorAttributes; + } else if (level.intValue() < Level.INFO.intValue()) { + attributes = debugAttributes; + } + + log(formatter.format(record), attributes); + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } + } + + /** + * Used to send console messages to the console. + */ + private class ConsoleOutputStream extends ByteArrayOutputStream { + private AttributeSet attributes; + + private ConsoleOutputStream(AttributeSet attributes) { + this.attributes = attributes; + } + + @Override + public void flush() { + String data = toString(); + if (data.length() == 0) return; + log(data, attributes); + reset(); + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/ObjectSwingMapper.java b/launcher/src/main/java/com/skcraft/launcher/swing/ObjectSwingMapper.java new file mode 100644 index 0000000..6c4957d --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/ObjectSwingMapper.java @@ -0,0 +1,181 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import com.google.common.base.Strings; +import lombok.NonNull; + +import javax.swing.*; +import javax.swing.text.JTextComponent; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class ObjectSwingMapper { + + private final List mappings = new ArrayList(); + private final Object object; + + public ObjectSwingMapper(@NonNull Object object) { + this.object = object; + } + + public void copyFromObject() { + for (FieldMapping mapping : mappings) { + mapping.copyFromObject(); + } + } + + public void copyFromSwing() { + for (FieldMapping mapping : mappings) { + mapping.copyFromSwing(); + } + } + + private void add(@NonNull FieldMapping mapping) { + mappings.add(mapping); + } + + private MutatorAccessorField getField(@NonNull String field, Class clazz) { + return new MutatorAccessorField(object, field, clazz); + } + + public void map(@NonNull final JTextComponent textComponent, String name) { + final MutatorAccessorField field = getField(name, String.class); + + add(new FieldMapping() { + @Override + public void copyFromObject() { + textComponent.setText(field.get()); + } + + @SuppressWarnings("unchecked") + @Override + public void copyFromSwing() { + field.set(Strings.emptyToNull(textComponent.getText())); + } + }); + } + + public void map(@NonNull final JSpinner spinner, String name) { + final MutatorAccessorField field = getField(name, int.class); + + add(new FieldMapping() { + @Override + public void copyFromObject() { + spinner.setValue(field.get()); + } + + @SuppressWarnings("unchecked") + @Override + public void copyFromSwing() { + field.set((Integer) spinner.getValue()); + } + }); + } + + public void map(@NonNull final JCheckBox check, String name) { + final MutatorAccessorField field = getField(name, boolean.class); + + add(new FieldMapping() { + @Override + public void copyFromObject() { + check.setSelected(field.get()); + } + + @SuppressWarnings("unchecked") + @Override + public void copyFromSwing() { + field.set(check.isSelected()); + } + }); + } + + public static interface FieldMapping { + void copyFromObject(); + void copyFromSwing(); + } + + public static class MutatorAccessorField { + private final Class clazz; + private final Object object; + private final Method mutator; + private final Method accessor; + + public MutatorAccessorField(Object object, String name, Class clazz) { + this.object = object; + this.clazz = clazz; + + Method mutator = null; + Method accessor = null; + for (Method method : object.getClass().getMethods()) { + if (isAccessor(method, name)) { + accessor = method; + } else if (isMutator(method, name)) { + mutator = method; + } + } + + if (accessor == null) { + throw new NoSuchMethodError("Failed to find accessor pair on " + + object.getClass().getCanonicalName() + " for " + name); + } + + if (mutator == null) { + throw new NoSuchMethodError("Failed to find mutator pair on " + + object.getClass().getCanonicalName() + " for " + name); + } + + this.mutator = mutator; + this.accessor = accessor; + } + + private boolean isAccessor(Method method, String name) { + String methodName = method.getName(); + Class[] paramTypes = method.getParameterTypes(); + Class returnType = method.getReturnType(); + + return (methodName.equalsIgnoreCase("get" + name) || + methodName.equalsIgnoreCase("is" + name)) && + paramTypes.length == 0 && + clazz.isAssignableFrom(returnType); + } + + private boolean isMutator(Method method, String name) { + String methodName = method.getName(); + Class[] paramTypes = method.getParameterTypes(); + + return methodName.equalsIgnoreCase("set" + name) && + paramTypes.length == 1 && + paramTypes[0].isAssignableFrom(clazz); + } + + @SuppressWarnings("unchecked") + public V get() { + try { + Object value = accessor.invoke(object); + return (V) value; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public void set(V value) { + try { + mutator.invoke(object, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/PopupMouseAdapter.java b/launcher/src/main/java/com/skcraft/launcher/swing/PopupMouseAdapter.java new file mode 100644 index 0000000..ea3f6cc --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/PopupMouseAdapter.java @@ -0,0 +1,33 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +/** + * An implementation of MouseAdapter that makes it easier to handle right click menus. + */ +public abstract class PopupMouseAdapter extends MouseAdapter { + + @Override + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) { + showPopup(e); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + showPopup(e); + } + } + + protected abstract void showPopup(MouseEvent e); + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/SelectionKeeper.java b/launcher/src/main/java/com/skcraft/launcher/swing/SelectionKeeper.java new file mode 100644 index 0000000..dd777a6 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/SelectionKeeper.java @@ -0,0 +1,50 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import lombok.NonNull; + +import javax.swing.*; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +public class SelectionKeeper implements ListSelectionListener, ListDataListener { + + private final JList list; + private Object lastSelected; + + private SelectionKeeper(@NonNull JList list) { + this.list = list; + } + + public void intervalAdded(ListDataEvent e) { + list.setSelectedValue(lastSelected, true); + } + + public void intervalRemoved(ListDataEvent e) { + list.setSelectedValue(lastSelected, true); + } + + public void contentsChanged(ListDataEvent e) { + list.setSelectedValue(lastSelected, true); + } + + public void valueChanged(ListSelectionEvent e) { + if (!e.getValueIsAdjusting()) { + lastSelected = list.getSelectedValue(); + } + } + + public static void attach(@NonNull JList list) { + SelectionKeeper s = new SelectionKeeper(list); + list.addListSelectionListener(s); + list.getModel().addListDataListener(s); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/SwingHelper.java b/launcher/src/main/java/com/skcraft/launcher/swing/SwingHelper.java new file mode 100644 index 0000000..7779d62 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/SwingHelper.java @@ -0,0 +1,378 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.skcraft.launcher.LauncherException; +import com.skcraft.launcher.util.SwingExecutor; +import lombok.NonNull; +import lombok.extern.java.Log; + +import javax.imageio.ImageIO; +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.plaf.basic.BasicSplitPaneDivider; +import javax.swing.plaf.basic.BasicSplitPaneUI; +import javax.swing.text.JTextComponent; +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.ClipboardOwner; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.image.BufferedImage; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.concurrent.CancellationException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; + +import static com.skcraft.launcher.util.SharedLocale._; +import static org.apache.commons.io.IOUtils.closeQuietly; + +/** + * Swing utility methods. + */ +@Log +public final class SwingHelper { + + private static final ClipboardOwner clipboardOwner = new ClipboardOwner() { + @Override + public void lostOwnership(Clipboard clipboard, Transferable contents) { + + } + }; + + private SwingHelper() { + } + + public static String htmlEscape(String str) { + return str.replace(">", ">") + .replace("<", "<") + .replace("&", "&"); + } + + public static void setClipboard(String text) { + Toolkit.getDefaultToolkit().getSystemClipboard().setContents( + new StringSelection(text), clipboardOwner); + } + + public static void browseDir(File file, Component component) { + try { + Desktop.getDesktop().open(file); + } catch (IOException e) { + JOptionPane.showMessageDialog(component, _("errors.openDirError", file.getAbsolutePath()), + _("errorTitle"), JOptionPane.ERROR_MESSAGE); + } + } + + /** + * Opens a system web browser for the given URL. + * + * @param url the URL + * @param parentComponent the component from which to show any errors + */ + public static void openURL(@NonNull String url, @NonNull Component parentComponent) { + try { + openURL(new URL(url), parentComponent); + } catch (MalformedURLException e) { + } + } + + /** + * Opens a system web browser for the given URL. + * + * @param url the URL + * @param parentComponent the component from which to show any errors + */ + public static void openURL(URL url, Component parentComponent) { + try { + Desktop.getDesktop().browse(url.toURI()); + } catch (IOException e) { + showErrorDialog(parentComponent, _("errors.openUrlError", url.toString()), _("errorTitle")); + } catch (URISyntaxException e) { + } + } + + /** + * Shows an popup error dialog, with potential extra details shown either immediately + * or available on the dialog. + * + * @param parentComponent the frame from which the dialog is displayed, otherwise + * null to use the default frame + * @param message the message to display + * @param title the title string for the dialog + * @see #showMessageDialog(java.awt.Component, String, String, String, int) for details + */ + public static void showErrorDialog(Component parentComponent, @NonNull String message, + @NonNull String title) { + showErrorDialog(parentComponent, message, title, null); + } + + /** + * Shows an popup error dialog, with potential extra details shown either immediately + * or available on the dialog. + * + * @param parentComponent the frame from which the dialog is displayed, otherwise + * null to use the default frame + * @param message the message to display + * @param title the title string for the dialog + * @param throwable the exception, or null if there is no exception to show + * @see #showMessageDialog(java.awt.Component, String, String, String, int) for details + */ + public static void showErrorDialog(Component parentComponent, @NonNull String message, + @NonNull String title, Throwable throwable) { + String detailsText = null; + + // Get a string version of the exception and use that for + // the extra details text + if (throwable != null) { + StringWriter sw = new StringWriter(); + throwable.printStackTrace(new PrintWriter(sw)); + detailsText = sw.toString(); + } + + showMessageDialog(parentComponent, + message, title, + detailsText, JOptionPane.ERROR_MESSAGE); + } + + /** + * Show a message dialog using + * {@link javax.swing.JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)}. + * + *

The dialog will be shown from the Event Dispatch Thread, regardless of the + * thread it is called from. In either case, the method will block until the + * user has closed the dialog (or dialog creation fails for whatever reason).

+ * + * @param parentComponent the frame from which the dialog is displayed, otherwise + * null to use the default frame + * @param message the message to display + * @param title the title string for the dialog + * @param messageType see {@link javax.swing.JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)} + * for available message types + */ + public static void showMessageDialog(final Component parentComponent, + @NonNull final String message, + @NonNull final String title, + final String detailsText, + final int messageType) { + + if (SwingUtilities.isEventDispatchThread()) { + // To force the label to wrap, convert the message to broken HTML + String htmlMessage = "
" + htmlEscape(message); + + JPanel panel = new JPanel(new BorderLayout(0, detailsText != null ? 20 : 0)); + + // Add the main message + panel.add(new JLabel(htmlMessage), BorderLayout.NORTH); + + // Add the extra details + if (detailsText != null) { + JTextArea textArea = new JTextArea(_("errors.reportErrorPreface") + detailsText); + JLabel tempLabel = new JLabel(); + textArea.setFont(tempLabel.getFont()); + textArea.setBackground(tempLabel.getBackground()); + textArea.setTabSize(2); + textArea.setEditable(false); + textArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + + JScrollPane scrollPane = new JScrollPane(textArea); + scrollPane.setPreferredSize(new Dimension(350, 120)); + panel.add(scrollPane, BorderLayout.CENTER); + } + + JOptionPane.showMessageDialog( + parentComponent, panel, title, messageType); + } else { + // Call method again from the Event Dispatch Thread + try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + showMessageDialog( + parentComponent, message, title, + detailsText, messageType); + } + }); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Asks the user a binary yes or no question. + * + * @param parentComponent the component + * @param message the message to display + * @param title the title string for the dialog + * @return whether 'yes' was selected + */ + public static boolean confirmDialog(final Component parentComponent, + @NonNull final String message, + @NonNull final String title) { + if (SwingUtilities.isEventDispatchThread()) { + return JOptionPane.showConfirmDialog( + parentComponent, message, title, JOptionPane.YES_NO_OPTION) == + JOptionPane.YES_OPTION; + } else { + // Use an AtomicBoolean to pass the result back from the + // Event Dispatcher Thread + final AtomicBoolean yesSelected = new AtomicBoolean(); + + try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + yesSelected.set(confirmDialog(parentComponent, title, message)); + } + }); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + + return yesSelected.get(); + } + } + + /** + * Equalize the width of the given components. + * + * @param component component + */ + public static void equalWidth(Component ... component) { + double widest = 0; + for (Component comp : component) { + Dimension dim = comp.getPreferredSize(); + if (dim.getWidth() > widest) { + widest = dim.getWidth(); + } + } + + for (Component comp : component) { + Dimension dim = comp.getPreferredSize(); + comp.setPreferredSize(new Dimension((int) widest, (int) dim.getHeight())); + } + } + + /** + * Remove all the opaqueness of the given components and child components. + * + * @param components list of components + */ + public static void removeOpaqueness(@NonNull Component ... components) { + for (Component component : components) { + if (component instanceof JComponent) { + JComponent jComponent = (JComponent) component; + jComponent.setOpaque(false); + removeOpaqueness(jComponent.getComponents()); + } + } + } + + public static BufferedImage readIconImage(Class clazz, String path) { + InputStream in = null; + try { + in = clazz.getResourceAsStream(path); + if (in != null) { + return ImageIO.read(in); + } + } catch (IOException e) { + } finally { + closeQuietly(in); + } + return null; + } + + public static void setIconImage(JFrame frame, Class clazz, String path) { + BufferedImage image = readIconImage(clazz, path); + if (image != null) { + frame.setIconImage(image); + } + } + + /** + * Focus a component. + * + *

The focus call happens in {@link javax.swing.SwingUtilities#invokeLater(Runnable)}.

+ * + * @param component the component + */ + public static void focusLater(@NonNull final Component component) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (component instanceof JTextComponent) { + ((JTextComponent) component).selectAll(); + } + component.requestFocusInWindow(); + } + }); + } + + public static void flattenJSplitPane(JSplitPane splitPane) { + splitPane.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); + BasicSplitPaneUI flatDividerSplitPaneUI = new BasicSplitPaneUI() { + @Override + public BasicSplitPaneDivider createDefaultDivider() { + return new BasicSplitPaneDivider(this) { + @Override + public void setBorder(Border b) { + } + }; + } + }; + splitPane.setUI(flatDividerSplitPaneUI); + splitPane.setBorder(null); + } + + public static void addErrorDialogCallback(final Window owner, ListenableFuture future) { + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(Object result) { + } + + @Override + public void onFailure(Throwable t) { + if (t instanceof InterruptedException || t instanceof CancellationException) { + return; + } + + String message; + if (t instanceof LauncherException) { + message = t.getLocalizedMessage(); + t = t.getCause(); + } else { + message = t.getLocalizedMessage(); + if (message == null) { + message = _("errors.genericError"); + } + } + log.log(Level.WARNING, "Task failed", t); + SwingHelper.showErrorDialog(owner, message, _("errorTitle"), t); + } + }, SwingExecutor.INSTANCE); + } + + public static Component alignTabbedPane(Component component) { + JPanel container = new JPanel(); + container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); + container.add(component); + container.add(new Box.Filler(new Dimension(0, 0), new Dimension(0, 10000), new Dimension(0, 10000))); + SwingHelper.removeOpaqueness(container); + return container; + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/TextFieldPopupMenu.java b/launcher/src/main/java/com/skcraft/launcher/swing/TextFieldPopupMenu.java new file mode 100644 index 0000000..07f6257 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/TextFieldPopupMenu.java @@ -0,0 +1,74 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import javax.swing.*; +import javax.swing.text.JTextComponent; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import static com.skcraft.launcher.util.SharedLocale._; + +public class TextFieldPopupMenu extends JPopupMenu implements ActionListener { + + public static final TextFieldPopupMenu INSTANCE = new TextFieldPopupMenu(); + + private final JMenuItem cutItem; + private final JMenuItem copyItem; + private final JMenuItem pasteItem; + private final JMenuItem deleteItem; + private final JMenuItem selectAllItem; + + private TextFieldPopupMenu() { + cutItem = addMenuItem(new JMenuItem(_("context.cut"), 'T')); + copyItem = addMenuItem(new JMenuItem(_("context.copy"), 'C')); + pasteItem = addMenuItem(new JMenuItem(_("context.paste"), 'P')); + deleteItem = addMenuItem(new JMenuItem(_("context.delete"), 'D')); + addSeparator(); + selectAllItem = addMenuItem(new JMenuItem(_("context.selectAll"), 'A')); + } + + private JMenuItem addMenuItem(JMenuItem item) { + item.addActionListener(this); + return add(item); + } + + @Override + public void show(Component invoker, int x, int y) { + JTextComponent textComponent = (JTextComponent) invoker; + boolean editable = textComponent.isEditable() && textComponent.isEnabled(); + cutItem.setVisible(editable); + pasteItem.setVisible(editable); + deleteItem.setVisible(editable); + super.show(invoker, x, y); + } + + @Override + public void actionPerformed(ActionEvent e) { + JTextComponent textComponent = (JTextComponent) getInvoker(); + textComponent.requestFocus(); + + boolean haveSelection = + textComponent.getSelectionStart() != textComponent.getSelectionEnd(); + + if (e.getSource() == cutItem) { + if (!haveSelection) textComponent.selectAll(); + textComponent.cut(); + } else if (e.getSource() == copyItem) { + if (!haveSelection) textComponent.selectAll(); + textComponent.copy(); + } else if (e.getSource() == pasteItem) { + textComponent.paste(); + } else if (e.getSource() == deleteItem) { + if (!haveSelection) textComponent.selectAll(); + textComponent.replaceSelection(""); + } else if (e.getSource() == selectAllItem) { + textComponent.selectAll(); + } + } +} \ No newline at end of file diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/WebpageLayoutManager.java b/launcher/src/main/java/com/skcraft/launcher/swing/WebpageLayoutManager.java new file mode 100644 index 0000000..74ee2de --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/WebpageLayoutManager.java @@ -0,0 +1,58 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import javax.swing.*; +import java.awt.*; + +public class WebpageLayoutManager implements LayoutManager { + + private static final int PROGRESS_WIDTH = 100; + + @Override + public void addLayoutComponent(String name, Component comp) { + } + + @Override + public void removeLayoutComponent(Component comp) { + throw new UnsupportedOperationException("Can't remove things!"); + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + return new Dimension(0, 0); + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return new Dimension(0, 0); + } + + @Override + public void layoutContainer(Container parent) { + Insets insets = parent.getInsets(); + int maxWidth = parent.getWidth() - (insets.left + insets.right); + int maxHeight = parent.getHeight() - (insets.top + insets.bottom); + + int numComps = parent.getComponentCount(); + for (int i = 0 ; i < numComps ; i++) { + Component comp = parent.getComponent(i); + + if (comp instanceof JProgressBar) { + Dimension size = comp.getPreferredSize(); + comp.setLocation((parent.getWidth() - PROGRESS_WIDTH) / 2, + (int) (parent.getHeight() / 2.0 - size.height / 2.0)); + comp.setSize(PROGRESS_WIDTH, + (int) comp.getPreferredSize().height); + } else { + comp.setLocation(insets.left, insets.top); + comp.setSize(maxWidth, maxHeight); + } + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/WebpagePanel.java b/launcher/src/main/java/com/skcraft/launcher/swing/WebpagePanel.java new file mode 100644 index 0000000..86d5231 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/WebpagePanel.java @@ -0,0 +1,271 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import com.skcraft.launcher.LauncherUtils; +import lombok.extern.java.Log; + +import javax.swing.*; +import javax.swing.border.CompoundBorder; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.html.HTMLDocument; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Enumeration; +import java.util.logging.Level; + +import static com.skcraft.launcher.LauncherUtils.checkInterrupted; + +@Log +public final class WebpagePanel extends JPanel { + + private final WebpagePanel self = this; + + private URL url; + private boolean activated; + private JEditorPane documentView; + private JProgressBar progressBar; + private Thread thread; + + public static WebpagePanel forURL(URL url, boolean lazy) { + return new WebpagePanel(url, lazy); + } + + public static WebpagePanel forHTML(String html) { + return new WebpagePanel(html); + } + + private WebpagePanel(URL url, boolean lazy) { + this.url = url; + + setLayout(new BorderLayout()); + + if (lazy) { + setPlaceholder(); + } else { + setDocument(); + fetchAndDisplay(url); + } + } + + private WebpagePanel(String text) { + this.url = null; + + setLayout(new BorderLayout()); + + setDocument(); + setDisplay(text, null); + } + + public WebpagePanel(boolean lazy) { + this.url = null; + + setLayout(new BorderLayout()); + + if (lazy) { + setPlaceholder(); + } else { + setDocument(); + } + } + + private void setDocument() { + activated = true; + + JLayeredPane panel = new JLayeredPane(); + panel.setLayout(new WebpageLayoutManager()); + + documentView = new JEditorPane(); + documentView.setBorder(null); + documentView.setEditable(false); + documentView.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + if (e.getURL() != null) { + SwingHelper.openURL(e.getURL(), self); + } + } + } + }); + + JScrollPane scrollPane = new JScrollPane(documentView); + panel.add(scrollPane, new Integer(1)); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + + progressBar = new JProgressBar(); + progressBar.setIndeterminate(true); + panel.add(progressBar, new Integer(2)); + + add(panel, BorderLayout.CENTER); + } + + private void setPlaceholder() { + activated = false; + + JLayeredPane panel = new JLayeredPane(); + panel.setBorder(new CompoundBorder( + BorderFactory.createEtchedBorder(), BorderFactory + .createEmptyBorder(4, 4, 4, 4))); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + final JButton showButton = new JButton("Load page"); + showButton.setAlignmentX(Component.CENTER_ALIGNMENT); + showButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showButton.setVisible(false); + setDocument(); + fetchAndDisplay(url); + } + }); + + // Center the button vertically. + panel.add(new Box.Filler( + new Dimension(0, 0), + new Dimension(0, 0), + new Dimension(1000, 1000))); + panel.add(showButton); + panel.add(new Box.Filler( + new Dimension(0, 0), + new Dimension(0, 0), + new Dimension(1000, 1000))); + + add(panel, BorderLayout.CENTER); + } + + /** + * Browse to a URL. + * + * @param url the URL + * @param onlyChanged true to only browse if the last URL was different + * @return true if only the URL was changed + */ + public boolean browse(URL url, boolean onlyChanged) { + if (onlyChanged && this.url != null && this.url.equals(url)) { + return false; + } + + this.url = url; + + if (activated) { + fetchAndDisplay(url); + } + + return true; + } + + /** + * Update the page. This has to be run in the Swing event thread. + * + * @param url the URL + */ + private synchronized void fetchAndDisplay(URL url) { + if (thread != null) { + thread.interrupt(); + } + + progressBar.setVisible(true); + + thread = new Thread(new FetchWebpage(url)); + thread.setDaemon(true); + thread.start(); + } + + private void setDisplay(String text, URL baseUrl) { + progressBar.setVisible(false); + documentView.setContentType("text/html"); + HTMLDocument document = (HTMLDocument) documentView.getDocument(); + + // Clear existing styles + Enumeration e = document.getStyleNames(); + while (e.hasMoreElements()) { + Object o = e.nextElement(); + document.removeStyle((String) o); + } + + document.setBase(baseUrl); + documentView.setText(text); + + documentView.setCaretPosition(0); + } + + private void setError(String text) { + progressBar.setVisible(false); + documentView.setContentType("text/plain"); + documentView.setText(text); + documentView.setCaretPosition(0); + } + + private class FetchWebpage implements Runnable { + private URL url; + + public FetchWebpage(URL url) { + this.url = url; + } + + @Override + public void run() { + HttpURLConnection conn = null; + + try { + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setUseCaches(false); + conn.setDoInput(true); + conn.setDoOutput(false); + conn.setReadTimeout(5000); + + conn.connect(); + + checkInterrupted(); + + if (conn.getResponseCode() != 200) { + throw new IOException( + "Did not get expected 200 code, got " + + conn.getResponseCode()); + } + + BufferedReader reader = new BufferedReader( + new InputStreamReader(conn.getInputStream(), + "UTF-8")); + + StringBuilder s = new StringBuilder(); + char[] buf = new char[1024]; + int len = 0; + while ((len = reader.read(buf)) != -1) { + s.append(buf, 0, len); + } + String result = s.toString(); + + checkInterrupted(); + + setDisplay(result, LauncherUtils.concat(url, "")); + } catch (IOException e) { + if (Thread.interrupted()) { + return; + } + + log.log(Level.WARNING, "Failed to fetch page", e); + setError("Failed to fetch page: " + e.getMessage()); + } catch (InterruptedException e) { + } finally { + if (conn != null) + conn.disconnect(); + conn = null; + } + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java b/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java new file mode 100644 index 0000000..9fa52a8 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/update/BaseUpdater.java @@ -0,0 +1,243 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.update; + +import com.google.common.base.Strings; +import com.skcraft.launcher.AssetsRoot; +import com.skcraft.launcher.Instance; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.LauncherException; +import com.skcraft.launcher.dialog.FeatureSelectionDialog; +import com.skcraft.launcher.dialog.ProgressDialog; +import com.skcraft.launcher.install.*; +import com.skcraft.launcher.model.minecraft.Asset; +import com.skcraft.launcher.model.minecraft.AssetsIndex; +import com.skcraft.launcher.model.minecraft.Library; +import com.skcraft.launcher.model.minecraft.VersionManifest; +import com.skcraft.launcher.model.modpack.Feature; +import com.skcraft.launcher.model.modpack.Manifest; +import com.skcraft.launcher.model.modpack.ManifestEntry; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.util.Environment; +import com.skcraft.launcher.util.HttpRequest; +import lombok.NonNull; +import lombok.extern.java.Log; + +import javax.swing.*; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import java.util.logging.Level; + +import static com.skcraft.launcher.LauncherUtils.checkInterrupted; +import static com.skcraft.launcher.LauncherUtils.concat; +import static com.skcraft.launcher.util.SharedLocale._; + +/** + * The base implementation of the various routines involved in downloading + * and updating Minecraft (including the launcher's modpacks), such as asset + * downloading, .jar downloading, and so on. + *

+ * Updating actually starts in {@link com.skcraft.launcher.update.Updater}, + * which is the update worker. This class exists to allow updaters that don't + * use the launcher's default modpack format to reuse these update + * routines. (It also makes the size of the Updater class smaller.) + */ +@Log +public abstract class BaseUpdater { + + private static final long JAR_SIZE_ESTIMATE = 5 * 1024 * 1024; + private static final long LIBRARY_SIZE_ESTIMATE = 3 * 1024 * 1024; + + private final Launcher launcher; + private final Environment environment = Environment.getInstance(); + private final List executeOnCompletion = new ArrayList(); + + protected BaseUpdater(@NonNull Launcher launcher) { + this.launcher = launcher; + } + + protected void complete() { + for (Runnable runnable : executeOnCompletion) { + runnable.run(); + } + } + + protected Manifest installPackage(@NonNull Installer installer, @NonNull Instance instance) throws Exception { + final File contentDir = instance.getContentDir(); + final File logPath = new File(instance.getDir(), "install_log.json"); + final File cachePath = new File(instance.getDir(), "update_cache.json"); + final File featuresPath = new File(instance.getDir(), "features.json"); + + final InstallLog previousLog = Persistence.read(logPath, InstallLog.class); + final InstallLog currentLog = new InstallLog(); + currentLog.setBaseDir(contentDir); + final UpdateCache updateCache = Persistence.read(cachePath, UpdateCache.class); + final FeatureCache featuresCache = Persistence.read(featuresPath, FeatureCache.class); + + Manifest manifest = HttpRequest + .get(instance.getManifestURL()) + .execute() + .expectResponseCode(200) + .returnContent() + .saveContent(instance.getManifestPath()) + .asJson(Manifest.class); + + if (manifest.getMinimumVersion() > Launcher.PROTOCOL_VERSION) { + throw new LauncherException("Update required", _("errors.updateRequiredError")); + } + + if (manifest.getBaseUrl() == null) { + manifest.setBaseUrl(instance.getManifestURL()); + } + + final List features = manifest.getFeatures(); + if (!features.isEmpty()) { + for (Feature feature : features) { + Boolean last = featuresCache.getSelected().get(feature.getName()); + if (last != null) { + feature.setSelected(last); + } + } + + Collections.sort(features); + + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + new FeatureSelectionDialog(ProgressDialog.getLastDialog(), features).setVisible(true); + } + }); + + for (Feature feature : features) { + featuresCache.getSelected().put(Strings.nullToEmpty(feature.getName()), feature.isSelected()); + } + } + + for (ManifestEntry entry : manifest.getTasks()) { + entry.install(installer, currentLog, updateCache, contentDir); + } + + executeOnCompletion.add(new Runnable() { + @Override + public void run() { + for (Map.Entry> entry : previousLog.getEntrySet()) { + for (String path : entry.getValue()) { + if (!currentLog.has(path)) { + new File(contentDir, path).delete(); + } + } + } + + writeDataFile(logPath, currentLog); + writeDataFile(cachePath, updateCache); + writeDataFile(featuresPath, featuresCache); + } + }); + + return manifest; + } + + protected void installJar(@NonNull Installer installer, + @NonNull File jarFile, + @NonNull URL url) throws InterruptedException { + // If the JAR does not exist, install it + if (!jarFile.exists()) { + List targets = new ArrayList(); + + File tempFile = installer.getDownloader().download(url, "", JAR_SIZE_ESTIMATE, jarFile.getName()); + installer.queue(new FileMover(tempFile, jarFile)); + log.info("Installing " + jarFile.getName() + " from " + url); + } + } + + protected void installAssets(@NonNull Installer installer, + @NonNull VersionManifest versionManifest, + @NonNull URL indexUrl, + @NonNull List sources) throws IOException, InterruptedException { + AssetsRoot assetsRoot = launcher.getAssets(); + + AssetsIndex index = HttpRequest + .get(indexUrl) + .execute() + .expectResponseCode(200) + .returnContent() + .saveContent(assetsRoot.getIndexPath(versionManifest)) + .asJson(AssetsIndex.class); + + // Keep track of duplicates + Set downloading = new HashSet(); + + for (Map.Entry entry : index.getObjects().entrySet()) { + checkInterrupted(); + + String hash = entry.getValue().getHash(); + String path = String.format("%s/%s", hash.subSequence(0, 2), hash); + File targetFile = assetsRoot.getObjectPath(entry.getValue()); + + if (!targetFile.exists() && !downloading.contains(path)) { + List urls = new ArrayList(); + for (URL sourceUrl : sources) { + try { + urls.add(concat(sourceUrl, path)); + } catch (MalformedURLException e) { + log.log(Level.WARNING, "Bad source URL for library: " + sourceUrl); + } + } + + File tempFile = installer.getDownloader().download( + urls, "", entry.getValue().getSize(), entry.getKey()); + installer.queue(new FileMover(tempFile, targetFile)); + log.info("Fetching " + path + " from " + urls); + downloading.add(path); + } + } + } + + protected void installLibraries(@NonNull Installer installer, + @NonNull VersionManifest versionManifest, + @NonNull File librariesDir, + @NonNull List sources) throws InterruptedException { + + for (Library library : versionManifest.getLibraries()) { + if (library.matches(environment)) { + checkInterrupted(); + + String path = library.getPath(environment); + File targetFile = new File(librariesDir, path); + + if (!targetFile.exists()) { + List urls = new ArrayList(); + for (URL sourceUrl : sources) { + try { + urls.add(concat(sourceUrl, path)); + } catch (MalformedURLException e) { + log.log(Level.WARNING, "Bad source URL for library: " + sourceUrl); + } + } + + File tempFile = installer.getDownloader().download(urls, "", LIBRARY_SIZE_ESTIMATE, + library.getName() + ".jar"); + installer.queue(new FileMover( tempFile, targetFile)); + log.info("Fetching " + path + " from " + urls); + } + } + } + } + + private static void writeDataFile(File path, Object object) { + try { + Persistence.write(path, object); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to write to " + path.getAbsolutePath() + + " for object " + object.getClass().getCanonicalName(), e); + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/update/HardResetter.java b/launcher/src/main/java/com/skcraft/launcher/update/HardResetter.java new file mode 100644 index 0000000..cd9100a --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/update/HardResetter.java @@ -0,0 +1,75 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.update; + +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.Instance; +import com.skcraft.launcher.LauncherUtils; +import com.skcraft.launcher.persistence.Persistence; +import lombok.NonNull; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.concurrent.Callable; + +import static com.skcraft.launcher.util.SharedLocale._; + +public class HardResetter implements Callable, ProgressObservable { + + private final Instance instance; + private File currentDir; + + public HardResetter(@NonNull Instance instance) { + this.instance = instance; + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return _("instanceResetter.resetting", instance.getTitle()); + } + + @Override + public Instance call() throws Exception { + instance.setInstalled(false); + instance.setUpdatePending(true); + Persistence.commitAndForget(instance); + + new File(instance.getDir(), "update_cache.json").delete(); + + removeDir(new File(instance.getContentDir(), "config")); + removeDir(new File(instance.getContentDir(), "mods")); + + return instance; + } + + private void removeDir(File dir) throws IOException, InterruptedException { + try { + if (dir.isDirectory()) { + currentDir = dir; + LauncherUtils.interruptibleDelete(dir, new ArrayList()); + } + } finally { + currentDir = null; + } + } + + public String toString() { + File dir = currentDir; + if (dir != null) { + return "Removing " + dir.getAbsolutePath(); + } else { + return "Working..."; + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/update/Remover.java b/launcher/src/main/java/com/skcraft/launcher/update/Remover.java new file mode 100644 index 0000000..12f406f --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/update/Remover.java @@ -0,0 +1,70 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.update; + +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.Instance; +import com.skcraft.launcher.LauncherException; +import com.skcraft.launcher.LauncherUtils; +import com.skcraft.launcher.persistence.Persistence; +import lombok.NonNull; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import static com.skcraft.launcher.LauncherUtils.checkInterrupted; +import static com.skcraft.launcher.util.SharedLocale._; + +public class Remover implements Callable, ProgressObservable { + + private final Instance instance; + + public Remover(@NonNull Instance instance) { + this.instance = instance; + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return _("instanceDeleter.deleting", instance.getDir()); + } + + @Override + public Instance call() throws Exception { + instance.setInstalled(false); + instance.setUpdatePending(true); + Persistence.commitAndForget(instance); + + checkInterrupted(); + + Thread.sleep(2000); + + List failures = new ArrayList(); + + try { + LauncherUtils.interruptibleDelete(instance.getDir(), failures); + } catch (IOException e) { + Thread.sleep(1000); + LauncherUtils.interruptibleDelete(instance.getDir(), failures); + } + + if (failures.size() > 0) { + throw new LauncherException(failures.size() + " failed to delete", + _("instanceDeleter.failures", failures.size())); + } + + return instance; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/update/Updater.java b/launcher/src/main/java/com/skcraft/launcher/update/Updater.java new file mode 100644 index 0000000..9cc0fdd --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/update/Updater.java @@ -0,0 +1,207 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.update; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.skcraft.concurrency.DefaultProgress; +import com.skcraft.concurrency.ProgressFilter; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.Instance; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.LauncherException; +import com.skcraft.launcher.install.Installer; +import com.skcraft.launcher.model.minecraft.VersionManifest; +import com.skcraft.launcher.model.modpack.Manifest; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.util.HttpRequest; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.extern.java.Log; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; + +import static com.skcraft.launcher.util.HttpRequest.url; +import static com.skcraft.launcher.util.SharedLocale._; + +@Log +public class Updater extends BaseUpdater implements Callable, ProgressObservable { + + private final ObjectMapper mapper = new ObjectMapper(); + private final Installer installer; + private final Launcher launcher; + private final Instance instance; + + @Getter @Setter + private boolean online; + + private List librarySources = new ArrayList(); + private List assetsSources = new ArrayList(); + + private ProgressObservable progress = new DefaultProgress(-1, _("instanceUpdater.preparingUpdate")); + + public Updater(@NonNull Launcher launcher, @NonNull Instance instance) { + super(launcher); + + this.installer = new Installer(launcher.getInstallerDir()); + this.launcher = launcher; + this.instance = instance; + + librarySources.add(launcher.propUrl("librariesSource")); + assetsSources.add(launcher.propUrl("assetsSource")); + } + + @Override + public Instance call() throws Exception { + log.info("Checking for an update for '" + instance.getName() + "'..."); + + boolean updateRequired = !instance.isInstalled(); + boolean updateDesired = (instance.isUpdatePending() || updateRequired); + boolean updateCapable = (instance.getManifestURL() != null); + + if (!online && updateRequired) { + log.info("Can't update " + instance.getTitle() + " because offline"); + String message = _("updater.updateRequiredButOffline"); + throw new LauncherException("Update required but currently offline", message); + } + + if (updateDesired && !updateCapable) { + if (updateRequired) { + log.info("Update required for " + instance.getTitle() + " but there is no manifest"); + String message = _("updater.updateRequiredButNoManifest"); + throw new LauncherException("Update required but no manifest", message); + } else { + log.info("Can't update " + instance.getTitle() + ", but update is not required"); + return instance; // Can't update + } + } + + if (updateDesired) { + log.info("Updating " + instance.getTitle() + "..."); + update(instance); + } else { + log.info("No update found for " + instance.getTitle()); + } + + return instance; + } + + private VersionManifest readVersionManifest(Manifest manifest) throws IOException, InterruptedException { + // Check whether the package manifest contains an embedded version manifest, + // otherwise we'll have to download the one for the given Minecraft version + VersionManifest version = manifest.getVersionManifest(); + if (version != null) { + mapper.writeValue(instance.getVersionPath(), version); + return version; + } else { + URL url = url(String.format( + launcher.getProperties().getProperty("versionManifestUrl"), + manifest.getGameVersion())); + + return HttpRequest + .get(url) + .execute() + .expectResponseCode(200) + .returnContent() + .saveContent(instance.getVersionPath()) + .asJson(VersionManifest.class); + } + } + + /** + * Update the given instance. + * + * @param instance the instance + * @throws IOException thrown on I/O error + * @throws InterruptedException thrown on interruption + * @throws ExecutionException thrown on execution error + */ + protected void update(Instance instance) throws Exception { + // Mark this instance as local + instance.setLocal(true); + Persistence.commitAndForget(instance); + + // Read manifest + log.info("Reading package manifest..."); + progress = new DefaultProgress(-1, _("instanceUpdater.readingManifest")); + Manifest manifest = installPackage(installer, instance); + + // Update instance from manifest + manifest.update(instance); + + // Read version manifest + log.info("Reading version manifest..."); + progress = new DefaultProgress(-1, _("instanceUpdater.readingVersion")); + VersionManifest version = readVersionManifest(manifest); + + progress = new DefaultProgress(-1, _("instanceUpdater.buildingDownloadList")); + + // Install the .jar + File jarPath = launcher.getJarPath(version); + URL jarSource = launcher.propUrl("jarUrl", version.getId()); + log.info("JAR at " + jarPath.getAbsolutePath() + ", fetched from " + jarSource); + installJar(installer, jarPath, jarSource); + + // Download libraries + log.info("Enumerating libraries to download..."); + + URL url = manifest.getLibrariesUrl(); + if (url != null) { + log.info("Added library source: " + url); + librarySources.add(url); + } + + progress = new DefaultProgress(-1, _("instanceUpdater.collectingLibraries")); + installLibraries(installer, version, launcher.getLibrariesDir(), librarySources); + + // Download assets + log.info("Enumerating assets to download..."); + progress = new DefaultProgress(-1, _("instanceUpdater.collectingAssets")); + installAssets(installer, version, launcher.propUrl("assetsIndexUrl", version.getAssetsIndex()), assetsSources); + + log.info("Executing download phase..."); + progress = ProgressFilter.between(installer.getDownloader(), 0, 0.98); + installer.download(); + + log.info("Executing install phase..."); + progress = ProgressFilter.between(installer, 0.98, 1); + installer.execute(); + + log.info("Completing..."); + complete(); + + // Update the instance's information + log.info("Writing instance information..."); + instance.setVersion(manifest.getVersion()); + instance.setUpdatePending(false); + instance.setInstalled(true); + instance.setLocal(true); + Persistence.commitAndForget(instance); + + log.log(Level.INFO, instance.getName() + + " has been updated to version " + manifest.getVersion() + "."); + } + + @Override + public double getProgress() { + return progress.getProgress(); + } + + @Override + public String getStatus() { + return progress.getStatus(); + } + + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/util/Environment.java b/launcher/src/main/java/com/skcraft/launcher/util/Environment.java new file mode 100644 index 0000000..828f827 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/Environment.java @@ -0,0 +1,55 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.util; + +import lombok.Data; + +/** + * Represents information about the current environment. + */ +@Data +public class Environment { + + private final Platform platform; + private final String platformVersion; + private final String arch; + + /** + * Get an instance of the current environment. + * + * @return the current environment + */ + public static Environment getInstance() { + return new Environment(detectPlatform(), System.getProperty("os.version"), System.getProperty("os.arch")); + } + + public String getArchBits() { + return arch.contains("64") ? "64" : "32"; + } + + /** + * Detect the current platform. + * + * @return the current platform + */ + public static Platform detectPlatform() { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("win")) + return Platform.WINDOWS; + if (osName.contains("mac")) + return Platform.MAC_OS_X; + if (osName.contains("solaris") || osName.contains("sunos")) + return Platform.SOLARIS; + if (osName.contains("linux")) + return Platform.LINUX; + if (osName.contains("unix")) + return Platform.LINUX; + + return Platform.UNKNOWN; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/util/HttpRequest.java b/launcher/src/main/java/com/skcraft/launcher/util/HttpRequest.java new file mode 100644 index 0000000..cc23121 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/HttpRequest.java @@ -0,0 +1,522 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.skcraft.concurrency.ProgressObservable; +import lombok.Getter; +import lombok.extern.java.Log; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import java.io.*; +import java.net.*; +import java.util.*; + +import static com.skcraft.launcher.LauncherUtils.checkInterrupted; +import static org.apache.commons.io.IOUtils.closeQuietly; + +/** + * A simple fluent interface for performing HTTP requests that uses + * {@link java.net.HttpURLConnection} or {@link javax.net.ssl.HttpsURLConnection}. + */ +@Log +public class HttpRequest implements Closeable, ProgressObservable { + + private static final int READ_TIMEOUT = 1000 * 60 * 10; + private static final int READ_BUFFER_SIZE = 1024 * 8; + + private final ObjectMapper mapper = new ObjectMapper(); + private final Map headers = new HashMap(); + private final String method; + @Getter + private final URL url; + private String contentType; + private byte[] body; + private HttpURLConnection conn; + private InputStream inputStream; + + private long contentLength = -1; + private long readBytes = 0; + + /** + * Create a new HTTP request. + * + * @param method the method + * @param url the URL + */ + private HttpRequest(String method, URL url) { + this.method = method; + this.url = url; + } + + /** + * Set the content body to a JSON object with the content type of "application/json". + * + * @param object the object to serialize as JSON + * @return this object + * @throws java.io.IOException if the object can't be mapped + */ + public HttpRequest bodyJson(Object object) throws IOException { + contentType = "application/json"; + body = mapper.writeValueAsBytes(object); + return this; + } + + /** + * Submit form data. + * + * @param form the form + * @return this object + */ + public HttpRequest bodyForm(Form form) { + contentType = "application/x-www-form-urlencoded"; + body = form.toString().getBytes(); + return this; + } + + /** + * Add a header. + * + * @param key the header key + * @param value the header value + * @return this object + */ + public HttpRequest header(String key, String value) { + headers.put(key, value); + return this; + } + + /** + * Execute the request. + *

+ * After execution, {@link #close()} should be called. + * + * @return this object + * @throws java.io.IOException on I/O error + */ + public HttpRequest execute() throws IOException { + boolean successful = false; + + try { + if (conn != null) { + throw new IllegalArgumentException("Connection already executed"); + } + + conn = (HttpURLConnection) reformat(url).openConnection(); + conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Java) SKMCLauncher"); + + if (body != null) { + conn.setRequestProperty("Content-Type", contentType); + conn.setRequestProperty("Content-Length", Integer.toString(body.length)); + conn.setDoInput(true); + } + + for (Map.Entry entry : headers.entrySet()) { + conn.setRequestProperty(entry.getKey(), entry.getValue()); + } + + conn.setRequestMethod(method); + conn.setUseCaches(false); + conn.setDoOutput(true); + conn.setReadTimeout(READ_TIMEOUT); + + conn.connect(); + + if (body != null) { + DataOutputStream out = new DataOutputStream(conn.getOutputStream()); + out.write(body); + out.flush(); + out.close(); + } + + inputStream = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? + conn.getInputStream() : conn.getErrorStream(); + + successful = true; + } finally { + if (!successful) { + close(); + } + } + + return this; + } + + /** + * Require that the response code is one of the given response codes. + * + * @param codes a list of codes + * @return this object + * @throws java.io.IOException if there is an I/O error or the response code is not expected + */ + public HttpRequest expectResponseCode(int... codes) throws IOException { + int responseCode = getResponseCode(); + + for (int code : codes) { + if (code == responseCode) { + return this; + } + } + + close(); + throw new IOException("Did not get expected response code, got " + responseCode + " for " + url); + } + + /** + * Get the response code. + * + * @return the response code + * @throws java.io.IOException on I/O error + */ + public int getResponseCode() throws IOException { + if (conn == null) { + throw new IllegalArgumentException("No connection has been made"); + } + + return conn.getResponseCode(); + } + + /** + * Get the input stream. + * + * @return the input stream + */ + public InputStream getInputStream() { + return inputStream; + } + + /** + * Buffer the returned response. + * + * @return the buffered response + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public BufferedResponse returnContent() throws IOException, InterruptedException { + if (inputStream == null) { + throw new IllegalArgumentException("No input stream available"); + } + + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int b = 0; + while ((b = inputStream.read()) != -1) { + checkInterrupted(); + bos.write(b); + } + return new BufferedResponse(bos.toByteArray()); + } finally { + close(); + } + } + + /** + * Save the result to a file. + * + * @param file the file + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public HttpRequest saveContent(File file) throws IOException, InterruptedException { + FileOutputStream fos = null; + BufferedOutputStream bos = null; + + try { + fos = new FileOutputStream(file); + bos = new BufferedOutputStream(fos); + + saveContent(bos); + } finally { + closeQuietly(bos); + closeQuietly(fos); + } + + return this; + } + + /** + * Save the result to an output stream. + * + * @param out the output stream + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public HttpRequest saveContent(OutputStream out) throws IOException, InterruptedException { + BufferedInputStream bis; + + try { + String field = conn.getHeaderField("Content-Length"); + if (field != null) { + long len = Long.parseLong(field); + if (len >= 0) { // Let's just not deal with really big numbers + contentLength = len; + } + } + } catch (NumberFormatException e) { + } + + try { + bis = new BufferedInputStream(inputStream); + + byte[] data = new byte[READ_BUFFER_SIZE]; + int len = 0; + while ((len = bis.read(data, 0, READ_BUFFER_SIZE)) >= 0) { + out.write(data, 0, len); + readBytes += len; + checkInterrupted(); + } + } finally { + close(); + } + + return this; + } + + @Override + public double getProgress() { + if (contentLength >= 0) { + return readBytes / (double) contentLength; + } else { + return -1; + } + } + + @Override + public String getStatus() { + return null; + } + + @Override + public void close() throws IOException { + if (conn != null) conn.disconnect(); + } + + /** + * Perform a GET request. + * + * @param url the URL + * @return a new request object + */ + public static HttpRequest get(URL url) { + return request("GET", url); + } + + /** + * Perform a POST request. + * + * @param url the URL + * @return a new request object + */ + public static HttpRequest post(URL url) { + return request("POST", url); + } + + /** + * Perform a request. + * + * @param method the method + * @param url the URL + * @return a new request object + */ + public static HttpRequest request(String method, URL url) { + return new HttpRequest(method, url); + } + + /** + * Create a new {@link java.net.URL} and throw a {@link RuntimeException} if the URL + * is not valid. + * + * @param url the url + * @return a URL object + * @throws RuntimeException if the URL is invalid + */ + public static URL url(String url) { + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + /** + * URL may contain spaces and other nasties that will cause a failure. + * + * @param existing the existing URL to transform + * @return the new URL, or old one if there was a failure + */ + private static URL reformat(URL existing) { + try { + URL url = new URL(existing.toString()); + URI uri = new URI( + url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), + url.getPath(), url.getQuery(), url.getRef()); + url = uri.toURL(); + return url; + } catch (MalformedURLException e) { + return existing; + } catch (URISyntaxException e) { + return existing; + } + } + + /** + * Used with {@link #bodyForm(Form)}. + */ + public final static class Form { + public final List elements = new ArrayList(); + + private Form() { + } + + /** + * Add a key/value to the form. + * + * @param key the key + * @param value the value + * @return this object + */ + public Form add(String key, String value) { + try { + elements.add(URLEncoder.encode(key, "UTF-8") + + "=" + URLEncoder.encode(value, "UTF-8")); + return this; + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for (String element : elements) { + if (first) { + first = false; + } else { + builder.append("&"); + } + builder.append(element); + } + return builder.toString(); + } + + /** + * Create a new form. + * + * @return a new form + */ + public static Form form() { + return new Form(); + } + } + + /** + * Used to buffer the response in memory. + */ + public class BufferedResponse { + private final byte[] data; + + private BufferedResponse(byte[] data) { + this.data = data; + } + + /** + * Return the result as bytes. + * + * @return the data + */ + public byte[] asBytes() { + return data; + } + + /** + * Return the result as a string. + * + * @param encoding the encoding + * @return the string + * @throws java.io.IOException on I/O error + */ + public String asString(String encoding) throws IOException { + return new String(data, encoding); + } + + /** + * Return the result as an instance of the given class that has been + * deserialized from a JSON payload. + * + * @return the object + * @throws java.io.IOException on I/O error + */ + public T asJson(Class cls) throws IOException { + return mapper.readValue(asString("UTF-8"), cls); + } + + /** + * Return the result as an instance of the given class that has been + * deserialized from a XML payload. + * + * @return the object + * @throws java.io.IOException on I/O error + */ + @SuppressWarnings("unchecked") + public T asXml(Class cls) throws IOException { + try { + JAXBContext context = JAXBContext.newInstance(cls); + Unmarshaller um = context.createUnmarshaller(); + return (T) um.unmarshal(new ByteArrayInputStream(data)); + } catch (JAXBException e) { + throw new IOException(e); + } + } + + /** + * Save the result to a file. + * + * @param file the file + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public BufferedResponse saveContent(File file) throws IOException, InterruptedException { + FileOutputStream fos = null; + BufferedOutputStream bos = null; + + file.getParentFile().mkdirs(); + + try { + fos = new FileOutputStream(file); + bos = new BufferedOutputStream(fos); + + saveContent(bos); + } finally { + closeQuietly(bos); + closeQuietly(fos); + } + + return this; + } + + /** + * Save the result to an output stream. + * + * @param out the output stream + * @return this object + * @throws java.io.IOException on I/O error + * @throws InterruptedException on interruption + */ + public BufferedResponse saveContent(OutputStream out) throws IOException, InterruptedException { + out.write(data); + + return this; + } + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/util/LimitLinesDocumentListener.java b/launcher/src/main/java/com/skcraft/launcher/util/LimitLinesDocumentListener.java new file mode 100644 index 0000000..92da7b1 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/LimitLinesDocumentListener.java @@ -0,0 +1,114 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.util; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; + +/** + * From http://tips4java.wordpress.com/2008/10/15/limit-lines-in-document/ + * + * @author Rob Camick + */ +public class LimitLinesDocumentListener implements DocumentListener { + private int maximumLines; + private boolean isRemoveFromStart; + + /** + * Specify the number of lines to be stored in the Document. Extra lines + * will be removed from the start or end of the Document, depending on + * the boolean value specified. + * + * @param maximumLines number of lines + * @param isRemoveFromStart true to remove from the start + */ + public LimitLinesDocumentListener(int maximumLines, + boolean isRemoveFromStart) { + setLimitLines(maximumLines); + this.isRemoveFromStart = isRemoveFromStart; + } + + /** + * Set the maximum number of lines to be stored in the Document + * + * @param maximumLines number of lines + */ + public void setLimitLines(int maximumLines) { + if (maximumLines < 1) { + throw new IllegalArgumentException("Maximum lines must be greater than 0"); + } + + this.maximumLines = maximumLines; + } + + @Override + public void insertUpdate(final DocumentEvent e) { + // Changes to the Document can not be done within the listener + // so we need to add the processing to the end of the EDT + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + removeLines(e); + } + }); + } + + @Override + public void removeUpdate(DocumentEvent e) { + } + + @Override + public void changedUpdate(DocumentEvent e) { + } + + private void removeLines(DocumentEvent e) { + // The root Element of the Document will tell us the total number + // of line in the Document. + + Document document = e.getDocument(); + Element root = document.getDefaultRootElement(); + + while (root.getElementCount() > maximumLines) { + if (isRemoveFromStart) { + removeFromStart(document, root); + } else { + removeFromEnd(document, root); + } + } + } + + private void removeFromStart(Document document, Element root) { + Element line = root.getElement(0); + int end = line.getEndOffset(); + + try { + document.remove(0, end); + } catch (BadLocationException ble) { + System.out.println(ble); + } + } + + private void removeFromEnd(Document document, Element root) { + // We use start minus 1 to make sure we remove the newline + // character of the previous line + + Element line = root.getElement(root.getElementCount() - 1); + int start = line.getStartOffset(); + int end = line.getEndOffset(); + + try { + document.remove(start - 1, end - start); + } catch (BadLocationException ble) { + System.out.println(ble); + } + } +} \ No newline at end of file diff --git a/launcher/src/main/java/com/skcraft/launcher/util/PastebinPoster.java b/launcher/src/main/java/com/skcraft/launcher/util/PastebinPoster.java new file mode 100644 index 0000000..53a9666 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/PastebinPoster.java @@ -0,0 +1,114 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.util; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; + +public class PastebinPoster { + private static final int CONNECT_TIMEOUT = 5000; + private static final int READ_TIMEOUT = 5000; + + public static void paste(String code, PasteCallback callback) { + PasteProcessor processor = new PasteProcessor(code, callback); + Thread thread = new Thread(processor); + thread.start(); + } + + public static interface PasteCallback { + public void handleSuccess(String url); + public void handleError(String err); + } + + private static class PasteProcessor implements Runnable { + private String code; + private PasteCallback callback; + + public PasteProcessor(String code, PasteCallback callback) { + this.code = code; + this.callback = callback; + } + + @Override + public void run() { + HttpURLConnection conn = null; + OutputStream out = null; + InputStream in = null; + + try { + URL url = new URL("http://pastebin.com/api/api_post.php"); + conn = (HttpURLConnection) url.openConnection(); + conn.setConnectTimeout(CONNECT_TIMEOUT); + conn.setReadTimeout(READ_TIMEOUT); + conn.setRequestMethod("POST"); + conn.addRequestProperty("Content-type", "application/x-www-form-urlencoded"); + conn.setInstanceFollowRedirects(false); + conn.setDoOutput(true); + out = conn.getOutputStream(); + + out.write(("api_option=paste" + + "&api_dev_key=" + URLEncoder.encode("4867eae74c6990dbdef07c543cf8f805", "utf-8") + + "&api_paste_code=" + URLEncoder.encode(code, "utf-8") + + "&api_paste_private=" + URLEncoder.encode("0", "utf-8") + + "&api_paste_name=" + URLEncoder.encode("", "utf-8") + + "&api_paste_expire_date=" + URLEncoder.encode("1D", "utf-8") + + "&api_paste_format=" + URLEncoder.encode("text", "utf-8") + + "&api_user_key=" + URLEncoder.encode("", "utf-8")).getBytes()); + out.flush(); + out.close(); + + if (conn.getResponseCode() == 200) { + in = conn.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String line; + StringBuilder response = new StringBuilder(); + while ((line = reader.readLine()) != null) { + response.append(line); + response.append("\r\n"); + } + reader.close(); + + String result = response.toString().trim(); + + if (result.matches("^https?://.*")) { + callback.handleSuccess(result.trim()); + } else { + String err = result.trim(); + if (err.length() > 100) { + err = err.substring(0, 100); + } + callback.handleError(err); + } + } else { + callback.handleError("An error occurred while uploading the text."); + } + } catch (IOException e) { + callback.handleError(e.getMessage()); + } finally { + if (conn != null) { + conn.disconnect(); + } + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + } + } + if (out != null) { + try { + out.close(); + } catch (IOException ignored) { + } + } + } + } + + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/util/Platform.java b/launcher/src/main/java/com/skcraft/launcher/util/Platform.java new file mode 100644 index 0000000..a338351 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/Platform.java @@ -0,0 +1,20 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.util; + +import javax.xml.bind.annotation.XmlEnumValue; + +/** + * Indicates the platform. + */ +public enum Platform { + @XmlEnumValue("windows") WINDOWS, + @XmlEnumValue("mac_os_x") MAC_OS_X, + @XmlEnumValue("linux") LINUX, + @XmlEnumValue("solaris") SOLARIS, + @XmlEnumValue("unknown") UNKNOWN +} \ No newline at end of file diff --git a/launcher/src/main/java/com/skcraft/launcher/util/SharedLocale.java b/launcher/src/main/java/com/skcraft/launcher/util/SharedLocale.java new file mode 100644 index 0000000..93df77a --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/SharedLocale.java @@ -0,0 +1,106 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.util; + +import lombok.NonNull; +import lombok.extern.java.Log; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.logging.Level; + +/** + * Handles loading a shared message {@link java.util.ResourceBundle}. + */ +@Log +public class SharedLocale { + + private static Locale locale = Locale.getDefault(); + private static ResourceBundle bundle; + + /** + * Get the current locale. + * + * @return the current locale + */ + public static Locale getLocale() { + return locale; + } + + /** + * Get the current resource bundle. + * + * @return the current resource bundle, or null if not available + */ + public static ResourceBundle getBundle() { + return bundle; + } + + /** + * Translate a string. + * + *

If the string is not available, then ${key} will be returned.

+ * + * @param key the key + * @return the translated string + */ + public static String _(String key) { + if (bundle != null) { + try { + return bundle.getString(key); + } catch (MissingResourceException e) { + log.log(Level.WARNING, "Failed to find message", e); + } + } + + return "${" + key + "}"; + } + + /** + * Format a translated string. + * + *

If the string is not available, then ${key}:args will be returned.

+ * + * @param key the key + * @param args arguments + * @return a translated string + */ + public static String _(String key, Object... args) { + if (bundle != null) { + try { + MessageFormat formatter = new MessageFormat(_(key)); + formatter.setLocale(getLocale()); + return formatter.format(args); + } catch (MissingResourceException e) { + log.log(Level.WARNING, "Failed to find message", e); + } + } + + return "${" + key + "}:" + args; + } + + /** + * Load a shared resource bundle. + * + * @param baseName the bundle name + * @param locale the locale + * @return true if loaded successfully + */ + public static boolean loadBundle(@NonNull String baseName, @NonNull Locale locale) { + try { + SharedLocale.locale = locale; + bundle = ResourceBundle.getBundle(baseName, locale, + SharedLocale.class.getClassLoader()); + return true; + } catch (MissingResourceException e) { + log.log(Level.SEVERE, "Failed to load resource bundle", e); + return false; + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/util/SimpleLogFormatter.java b/launcher/src/main/java/com/skcraft/launcher/util/SimpleLogFormatter.java new file mode 100644 index 0000000..4cd9cc4 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/SimpleLogFormatter.java @@ -0,0 +1,63 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.util; + +import lombok.extern.java.Log; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.*; + +@Log +public final class SimpleLogFormatter extends Formatter { + + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + @Override + public String format(LogRecord record) { + StringBuilder sb = new StringBuilder(); + + sb.append("[") + .append(record.getLevel().getLocalizedName().toLowerCase()) + .append("] ") + .append(formatMessage(record)) + .append(LINE_SEPARATOR); + + if (record.getThrown() != null) { + try { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + record.getThrown().printStackTrace(pw); + pw.close(); + sb.append(sw.toString()); + } catch (Exception e) { + } + } + + return sb.toString(); + } + + public static void configureGlobalLogger() { + Logger globalLogger = Logger.getLogger(""); + + // Set formatter + for (Handler handler : globalLogger.getHandlers()) { + handler.setFormatter(new SimpleLogFormatter()); + } + + // Set level + String logLevel = System.getProperty( + SimpleLogFormatter.class.getCanonicalName() + ".logLevel", "INFO"); + try { + Level level = Level.parse(logLevel); + globalLogger.setLevel(level); + } catch (IllegalArgumentException e) { + log.log(Level.WARNING, "Invalid log level of " + logLevel, e); + } + } + +} \ No newline at end of file diff --git a/launcher/src/main/java/com/skcraft/launcher/util/SwingExecutor.java b/launcher/src/main/java/com/skcraft/launcher/util/SwingExecutor.java new file mode 100644 index 0000000..d3e24ec --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/SwingExecutor.java @@ -0,0 +1,63 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.util; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +public final class SwingExecutor extends AbstractExecutorService { + + public static final SwingExecutor INSTANCE = new SwingExecutor(); + + private SwingExecutor() { + } + + @Override + public void execute(Runnable runnable) { + SwingUtilities.invokeLater(runnable); + } + + @Override + protected RunnableFuture newTaskFor(final Callable callable) { + return new FutureTask(callable) { + @Override + public void run() { + try { + super.run(); + } catch (Throwable e) { + setException(e); + } + } + }; + } + + @Override + public void shutdown() { + } + + @Override + public List shutdownNow() { + return new ArrayList(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return false; + } +} \ No newline at end of file diff --git a/launcher/src/main/java/com/skcraft/launcher/util/WinRegistry.java b/launcher/src/main/java/com/skcraft/launcher/util/WinRegistry.java new file mode 100644 index 0000000..9e51983 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/WinRegistry.java @@ -0,0 +1,371 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.prefs.Preferences; + +public class WinRegistry { + public static final int HKEY_CURRENT_USER = 0x80000001; + public static final int HKEY_LOCAL_MACHINE = 0x80000002; + public static final int REG_SUCCESS = 0; + public static final int REG_NOTFOUND = 2; + public static final int REG_ACCESSDENIED = 5; + + private static final int KEY_ALL_ACCESS = 0xf003f; + private static final int KEY_READ = 0x20019; + private static Preferences userRoot = Preferences.userRoot(); + private static Preferences systemRoot = Preferences.systemRoot(); + private static Class userClass = userRoot.getClass(); + private static Method regOpenKey = null; + private static Method regCloseKey = null; + private static Method regQueryValueEx = null; + private static Method regEnumValue = null; + private static Method regQueryInfoKey = null; + private static Method regEnumKeyEx = null; + private static Method regCreateKeyEx = null; + private static Method regSetValueEx = null; + private static Method regDeleteKey = null; + private static Method regDeleteValue = null; + + static { + try { + regOpenKey = userClass.getDeclaredMethod("WindowsRegOpenKey", + new Class[] { int.class, byte[].class, int.class }); + regOpenKey.setAccessible(true); + regCloseKey = userClass.getDeclaredMethod("WindowsRegCloseKey", + new Class[] { int.class }); + regCloseKey.setAccessible(true); + regQueryValueEx = userClass.getDeclaredMethod( + "WindowsRegQueryValueEx", new Class[] { int.class, + byte[].class }); + regQueryValueEx.setAccessible(true); + regEnumValue = userClass.getDeclaredMethod("WindowsRegEnumValue", + new Class[] { int.class, int.class, int.class }); + regEnumValue.setAccessible(true); + regQueryInfoKey = userClass.getDeclaredMethod( + "WindowsRegQueryInfoKey1", new Class[] { int.class }); + regQueryInfoKey.setAccessible(true); + regEnumKeyEx = userClass.getDeclaredMethod("WindowsRegEnumKeyEx", + new Class[] { int.class, int.class, int.class }); + regEnumKeyEx.setAccessible(true); + regCreateKeyEx = userClass.getDeclaredMethod( + "WindowsRegCreateKeyEx", new Class[] { int.class, + byte[].class }); + regCreateKeyEx.setAccessible(true); + regSetValueEx = userClass.getDeclaredMethod("WindowsRegSetValueEx", + new Class[] { int.class, byte[].class, byte[].class }); + regSetValueEx.setAccessible(true); + regDeleteValue = userClass.getDeclaredMethod( + "WindowsRegDeleteValue", new Class[] { int.class, + byte[].class }); + regDeleteValue.setAccessible(true); + regDeleteKey = userClass.getDeclaredMethod("WindowsRegDeleteKey", + new Class[] { int.class, byte[].class }); + regDeleteKey.setAccessible(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private WinRegistry() { + } + + /** + * Read a value from key and value name + * + * @param hkey + * HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @param valueName + * @return the value + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + public static String readString(int hkey, String key, String valueName) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + if (hkey == HKEY_LOCAL_MACHINE) { + return readString(systemRoot, hkey, key, valueName); + } else if (hkey == HKEY_CURRENT_USER) { + return readString(userRoot, hkey, key, valueName); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + /** + * Read value(s) and value name(s) form given key + * + * @param hkey + * HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @return the value name(s) plus the value(s) + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + public static Map readStringValues(int hkey, String key) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + if (hkey == HKEY_LOCAL_MACHINE) { + return readStringValues(systemRoot, hkey, key); + } else if (hkey == HKEY_CURRENT_USER) { + return readStringValues(userRoot, hkey, key); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + /** + * Read the value name(s) from a given key + * + * @param hkey + * HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @return the value name(s) + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + public static List readStringSubKeys(int hkey, String key) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + if (hkey == HKEY_LOCAL_MACHINE) { + return readStringSubKeys(systemRoot, hkey, key); + } else if (hkey == HKEY_CURRENT_USER) { + return readStringSubKeys(userRoot, hkey, key); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + /** + * Create a key + * + * @param hkey + * HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE + * @param key + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + public static void createKey(int hkey, String key) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + int[] ret; + if (hkey == HKEY_LOCAL_MACHINE) { + ret = createKey(systemRoot, hkey, key); + regCloseKey + .invoke(systemRoot, new Object[] { new Integer(ret[0]) }); + } else if (hkey == HKEY_CURRENT_USER) { + ret = createKey(userRoot, hkey, key); + regCloseKey.invoke(userRoot, new Object[] { new Integer(ret[0]) }); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + if (ret[1] != REG_SUCCESS) { + throw new IllegalArgumentException("rc=" + ret[1] + " key=" + key); + } + } + + /** + * Write a value in a given key/value name + * + * @param hkey + * @param key + * @param valueName + * @param value + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + public static void writeStringValue(int hkey, String key, String valueName, + String value) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + if (hkey == HKEY_LOCAL_MACHINE) { + writeStringValue(systemRoot, hkey, key, valueName, value); + } else if (hkey == HKEY_CURRENT_USER) { + writeStringValue(userRoot, hkey, key, valueName, value); + } else { + throw new IllegalArgumentException("hkey=" + hkey); + } + } + + /** + * Delete a given key + * + * @param hkey + * @param key + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + public static void deleteKey(int hkey, String key) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + int rc = -1; + if (hkey == HKEY_LOCAL_MACHINE) { + rc = deleteKey(systemRoot, hkey, key); + } else if (hkey == HKEY_CURRENT_USER) { + rc = deleteKey(userRoot, hkey, key); + } + if (rc != REG_SUCCESS) { + throw new IllegalArgumentException("rc=" + rc + " key=" + key); + } + } + + /** + * delete a value from a given key/value name + * + * @param hkey + * @param key + * @param value + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws java.lang.reflect.InvocationTargetException + */ + public static void deleteValue(int hkey, String key, String value) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + int rc = -1; + if (hkey == HKEY_LOCAL_MACHINE) { + rc = deleteValue(systemRoot, hkey, key, value); + } else if (hkey == HKEY_CURRENT_USER) { + rc = deleteValue(userRoot, hkey, key, value); + } + if (rc != REG_SUCCESS) { + throw new IllegalArgumentException("rc=" + rc + " key=" + key + + " value=" + value); + } + } + + // ===================== + + private static int deleteValue(Preferences root, int hkey, String key, + String value) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + int[] handles = (int[]) regOpenKey.invoke(root, new Object[] { + new Integer(hkey), toCstr(key), new Integer(KEY_ALL_ACCESS) }); + if (handles[1] != REG_SUCCESS) { + return handles[1]; // can be REG_NOTFOUND, REG_ACCESSDENIED + } + int rc = ((Integer) regDeleteValue.invoke(root, new Object[] { + new Integer(handles[0]), toCstr(value) })).intValue(); + regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) }); + return rc; + } + + private static int deleteKey(Preferences root, int hkey, String key) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + int rc = ((Integer) regDeleteKey.invoke(root, new Object[] { + new Integer(hkey), toCstr(key) })).intValue(); + return rc; // can REG_NOTFOUND, REG_ACCESSDENIED, REG_SUCCESS + } + + private static String readString(Preferences root, int hkey, String key, + String value) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + int[] handles = (int[]) regOpenKey.invoke(root, new Object[] { + new Integer(hkey), toCstr(key), new Integer(KEY_READ) }); + if (handles[1] != REG_SUCCESS) { + return null; + } + byte[] valb = (byte[]) regQueryValueEx.invoke(root, new Object[] { + new Integer(handles[0]), toCstr(value) }); + regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) }); + return (valb != null ? new String(valb).trim() : null); + } + + private static Map readStringValues(Preferences root, + int hkey, String key) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + HashMap results = new HashMap(); + int[] handles = (int[]) regOpenKey.invoke(root, new Object[] { + new Integer(hkey), toCstr(key), new Integer(KEY_READ) }); + if (handles[1] != REG_SUCCESS) { + return null; + } + int[] info = (int[]) regQueryInfoKey.invoke(root, + new Object[] { new Integer(handles[0]) }); + + int count = info[0]; // count + int maxlen = info[3]; // value length max + for (int index = 0; index < count; index++) { + byte[] name = (byte[]) regEnumValue.invoke(root, new Object[] { + new Integer(handles[0]), new Integer(index), + new Integer(maxlen + 1) }); + String value = readString(hkey, key, new String(name)); + results.put(new String(name).trim(), value); + } + regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) }); + return results; + } + + private static List readStringSubKeys(Preferences root, int hkey, + String key) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + List results = new ArrayList(); + int[] handles = (int[]) regOpenKey.invoke(root, new Object[] { + new Integer(hkey), toCstr(key), new Integer(KEY_READ) }); + if (handles[1] != REG_SUCCESS) { + return null; + } + int[] info = (int[]) regQueryInfoKey.invoke(root, + new Object[] { new Integer(handles[0]) }); + + int count = info[0]; // Fix: info[2] was being used here with wrong + // results. Suggested by davenpcj, confirmed by + // Petrucio + int maxlen = info[3]; // value length max + for (int index = 0; index < count; index++) { + byte[] name = (byte[]) regEnumKeyEx.invoke(root, new Object[] { + new Integer(handles[0]), new Integer(index), + new Integer(maxlen + 1) }); + results.add(new String(name).trim()); + } + regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) }); + return results; + } + + private static int[] createKey(Preferences root, int hkey, String key) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + return (int[]) regCreateKeyEx.invoke(root, new Object[] { + new Integer(hkey), toCstr(key) }); + } + + private static void writeStringValue(Preferences root, int hkey, + String key, String valueName, String value) + throws IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + int[] handles = (int[]) regOpenKey.invoke(root, new Object[] { + new Integer(hkey), toCstr(key), new Integer(KEY_ALL_ACCESS) }); + + regSetValueEx.invoke(root, new Object[] { new Integer(handles[0]), + toCstr(valueName), toCstr(value) }); + regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) }); + } + + // utility + private static byte[] toCstr(String str) { + byte[] result = new byte[str.length() + 1]; + + for (int i = 0; i < str.length(); i++) { + result[i] = (byte) str.charAt(i); + } + result[str.length()] = 0; + return result; + } +} \ No newline at end of file diff --git a/launcher/src/main/resources/com/skcraft/launcher/custom_instance_icon.png b/launcher/src/main/resources/com/skcraft/launcher/custom_instance_icon.png new file mode 100644 index 0000000..867849c --- /dev/null +++ b/launcher/src/main/resources/com/skcraft/launcher/custom_instance_icon.png Binary files differ diff --git a/launcher/src/main/resources/com/skcraft/launcher/download_icon.png b/launcher/src/main/resources/com/skcraft/launcher/download_icon.png new file mode 100644 index 0000000..7a76bcb --- /dev/null +++ b/launcher/src/main/resources/com/skcraft/launcher/download_icon.png Binary files differ diff --git a/launcher/src/main/resources/com/skcraft/launcher/icon.png b/launcher/src/main/resources/com/skcraft/launcher/icon.png new file mode 100644 index 0000000..8c74095 --- /dev/null +++ b/launcher/src/main/resources/com/skcraft/launcher/icon.png Binary files differ diff --git a/launcher/src/main/resources/com/skcraft/launcher/instance_icon.png b/launcher/src/main/resources/com/skcraft/launcher/instance_icon.png new file mode 100644 index 0000000..8c74095 --- /dev/null +++ b/launcher/src/main/resources/com/skcraft/launcher/instance_icon.png Binary files differ diff --git a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties new file mode 100644 index 0000000..18996ee --- /dev/null +++ b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties @@ -0,0 +1,182 @@ +# +# SK's Minecraft Launcher +# Copyright (C) 2010-2014 Albert Pham and contributors +# Please see LICENSE.txt for license information. +# + +errorTitle=An error has occurred +confirmTitle=Confirm + +context.cut=Cut +context.copy=Copy +context.paste=Paste +context.delete=Delete +context.selectAll=Select all + +errors.openUrlError=Failed to open URL\: {0} +errors.openDirError=Unable to open ''{0}''. Maybe it doesn''t exist? +errors.reportErrorPreface=To report this error, please provide\:\n\n +errors.genericError=An error has occurred. +errors.updateRequiredError=Please download a new version of the launcher to continue updating. See skcraft.com for more information. +errors.selfUpdateCheckError=Checking for an update to the launcher has failed. + +button.cancel=Cancel +button.ok=OK + +options.title = Options +options.useProxyCheck = Use following proxy in Minecraft +options.jvmPath=JVM path\: +options.jvmArguments=JVM arguments\: +options.64BitJavaWarning=Make sure to have 64-bit Java installed if you are planning to set the memory limits higher. +options.minMemory=Minimum memory (MB)\: +options.maxMemory=Maximum memory (MB)\: +options.permGen=PermGen (MB)\: +options.javaTab=Java +options.windowWidth=Window width\: +options.windowHeight=Window height\: +options.minecraftTab=Minecraft +options.proxyHost=Proxy host\: +options.proxyPort=Proxy port\: +options.proxyUsername=Proxy username\: +options.proxyPassword=Proxy password\: +options.proxyTab=Proxy +options.gameKey=Game key\: +options.advancedTab=Advanced +options.launcherConsole=Launcher console + +instance.openFolder=View folder +instance.openSaves=View saves +instance.openResourcePacks=View resource packs +instance.openScreenshots=View screenshots +instance.copyAsPath=Copy as path +instance.forceUpdate=Force update +instance.hardForceUpdate=Hard force update... +instance.deleteFiles=Delete files... +instance.confirmDelete=Are you sure that you wish to delete ALL THE FILES (screenshots, worlds, configs) for ''{0}''? +instance.deletingTitle=Deleting instance... +instance.deletingStatus=Deleting ''{0}'' and files... +instance.confirmHardUpdate=A hard force update will delete the contents of config/ and mods/ and then require an update. Are you sure that you want to continue? +instance.resettingTitle=Resetting instance... +instance.resettingStatus=Resetting ''{0}''... + +launcher.launch=Launch... +launcher.checkForUpdates=Check for updates +launcher.options=Options... +launcher.updateLauncher=Update launcher... +launcher.downloadUpdates=Download modpack updates +launcher.title=SKCraft Launcher (v{0}) +launcher.refreshList=Refresh list +launcher.checkingTitle=Getting available modpacks... +launcher.checkingStatus=Getting available modpacks... Please wait. +launcher.selfUpdatingTitle=Updating launcher... +launcher.selfUpdatingStatus=Downloading launcher update... +launcher.selfUpdateComplete=Restart the launcher to use the new version. +launcher.selfUpdateCompleteTitle=Update complete +launcher.updatingTitle=Updating... +launcher.updatingStatus=Updating ''{0}''... Please wait. +launcher.noInstanceError=Please select a modpack to launch. +launcher.noInstanceTitle=No Modpack Selected +launcher.launchingTItle=Launching the game... +launcher.launchingStatus=Launching ''{0}''. Please wait. +launcher.modpackColumn=Modpack +launcher.notInstalledHint=(not installed) +launcher.requiresUpdateHint=(requires update) +launcher.updatePendingHint=(pending update) + +login.rememberId=Remember my account in the list +login.rememberPassword=Remember my password +login.login=Login... +login.recoverAccount=Forgot your login? +login.playOffline=Play offline +login.title=Minecraft Login +login.idEmail=ID/Email\: +login.password=Password\: +login.forgetUser=Forget selected user +login.forgetPassword=Forget password +login.forgetAllPasswords=Forget all passwords... +login.confirmForgetAllPasswords=Are you sure that you want to forget all saved passwords? +login.forgetAllPasswordsTitle=Forget passwords +login.noPasswordError=Please enter a password. +login.noPasswordTitle=Missing Password +login.loggingInTitle=Logging in... +login.loggingInStatus=Logging in to Mojang... +login.noLoginError=Please enter your account details. +login.noLoginTitle=Missing Account +login.minecraftNotOwnedError=Sorry, Minecraft is not owned on that account. + +console.title=Messages and Errors +console.launcherConsoleTitle=Launcher Messages +console.uploadLog=Upload Log +console.pasteUploading=Uploading {0} bytes...\n +console.pasteUploaded=Paste uploaded\: {0}\n +console.pasteFailed=Upload failed\: {0}\n +console.processEndCode=Process ended with code\: {0} +console.attachedToProcess=The game is running. Please wait. +console.forceClose=Force Close +console.trayTooltip=SKCraft Launcher +console.trayTitle=SKCraft Launcher +console.tray.showWindow=Show window +console.tray.forceClose=Force close... +console.closeWindow=Close Window +console.hideWindow=Hide Window +console.confirmKill=Are sure that you wish to close the game forcefully? You may lose data. +console.confirmKillTitle=Are you sure? + +downloader.downloadingItem=Downloading {0}... +downloader.downloadingList=Downloading {0} files... ({1} remaining, {2} failed) +downloader.jobProgress={1,number}%\t{0} +downloader.jobPending=...\t{0} +downloader.noDownloads=No pending downloads. +downloader.failedCount=({0} have failed) + +progress.details=Details... +progress.less=Less... +progress.viewLog=View log +progress.confirmCancel=Are you sure that you wish to cancel? +progress.confirmCancelTitle=Cancel +progress.defaultStatus=Working... +progress.percentTitle=({0}%) {1} + +installer.installing=Installing... +installer.executing=Executing tasks... ({0} remaining) +installer.copyingFile=Copying from {0} to {1} +installer.movingFile=Moving {0} to {1} + +updater.updating=Updating launcher... +updater.updateRequiredButOffline=An update is required but you need to be in online mode. +updater.updateRequiredButNoManifest=An update is required but update information for this instance is no longer available. + +instanceUpdater.preparingUpdate=Preparing to update... +instanceUpdater.readingManifest=Reading package manifest... +instanceUpdater.readingVersion=Reading version manifest... +instanceUpdater.buildingDownloadList=Collecting files to download... +instanceUpdater.collectingLibraries=Collecting libraries to download... +instanceUpdater.collectingAssets=Collecting assets to download... + +instanceDeleter.deleting=Deleting {0}... +instanceDeleter.failures={0} file(s) could not be deleted. + +instanceResetter.resetting=Resetting {0}... +instanceLoader.loadingLocal=Loading local instances from disk... +instanceLoader.checkingRemote=Checking for new modpacks... + +runner.preparing=Preparing for launch... +runner.collectingArgs=Collecting process arguments... +runner.startingJava=Starting java... +runner.updateRequired=This instance must be updated before it can be run. +runner.missingLibrary={0} needs to be relaunched and updated because the library ''{1}'' is missing. +runner.missingAssetsIndex={0} needs to be relaunched and updated because its asset index is missing. +runner.corruptAssetsIndex={0} needs to be relaunched and updated because its asset index is corrupt. + +assets.expanding1=Expanding {0} asset... ({1} remaining) +assets.expandingN=Expanding {0} assets... ({1} remaining) +assets.missingIndex=You need to update this instance because its index file at ''{0}'' is missing. +assets.missingObject=You need to update this instance because the file at ''{0}'' is missing. + +features.nameColumn=Feature +features.title=Select Features +features.install=OK +features.selectForInfo=Select a feature to see more information. +features.intro=Please select the optional features to install. +features.starred=(recommended) +features.avoid=(not recommended) diff --git a/launcher/src/main/resources/com/skcraft/launcher/launcher.properties b/launcher/src/main/resources/com/skcraft/launcher/launcher.properties new file mode 100644 index 0000000..7dbeb20 --- /dev/null +++ b/launcher/src/main/resources/com/skcraft/launcher/launcher.properties @@ -0,0 +1,21 @@ +# +# SK's Minecraft Launcher +# Copyright (C) 2010-2014 Albert Pham and contributors +# Please see LICENSE.txt for license information. +# + +version=${project.version} +agentName=Minecraft +offlinePlayerName=Player + +versionManifestUrl=https://s3.amazonaws.com/Minecraft.Download/versions/%1$s/%1$s.json +librariesSource=https://libraries.minecraft.net/ +jarUrl=http://s3.amazonaws.com/Minecraft.Download/versions/%1$s/%1$s.jar +assetsIndexUrl=https://s3.amazonaws.com/Minecraft.Download/indexes/%s.json +assetsSource=http://resources.download.minecraft.net/ +yggdrasilAuthUrl=https://authserver.mojang.com/authenticate +resetPasswordUrl=https://minecraft.net/resetpassword + +newsUrl=http://update.skcraft.com/template/news.html?version=%s +packageListUrl=http://update.skcraft.com/template/packages.json?key=%s +selfUpdateUrl=http://update.skcraft.com/template/launcher/latest.json diff --git a/launcher/src/main/resources/com/skcraft/launcher/maven_repos.json b/launcher/src/main/resources/com/skcraft/launcher/maven_repos.json new file mode 100644 index 0000000..eecbdb4 --- /dev/null +++ b/launcher/src/main/resources/com/skcraft/launcher/maven_repos.json @@ -0,0 +1,5 @@ +[ + "https://libraries.minecraft.net/", + "https://central.maven.org/maven2/", + "http://maven.apache.org/" +] \ No newline at end of file diff --git a/launcher/src/main/resources/com/skcraft/launcher/tray_closed.png b/launcher/src/main/resources/com/skcraft/launcher/tray_closed.png new file mode 100644 index 0000000..5e70e5b --- /dev/null +++ b/launcher/src/main/resources/com/skcraft/launcher/tray_closed.png Binary files differ diff --git a/launcher/src/main/resources/com/skcraft/launcher/tray_ok.png b/launcher/src/main/resources/com/skcraft/launcher/tray_ok.png new file mode 100644 index 0000000..867849c --- /dev/null +++ b/launcher/src/main/resources/com/skcraft/launcher/tray_ok.png Binary files differ diff --git a/pom.xml b/pom.xml deleted file mode 100644 index cf35f51..0000000 --- a/pom.xml +++ /dev/null @@ -1,130 +0,0 @@ - - - - 4.0.0 - - com.skcraft - launcher - 4.2.3-SNAPSHOT - - - - com.fasterxml.jackson.core - jackson-databind - 2.3.0 - - - commons-lang - commons-lang - 2.6 - - - commons-io - commons-io - 1.2 - - - org.projectlombok - lombok - 1.12.2 - - - com.google.guava - guava - 15.0 - - - com.beust - jcommander - 1.32 - - - org.tukaani - xz - 1.0 - - - org.apache.commons - commons-compress - 1.9 - - - - - SKCraftLauncher - - - - src/main/resources - true - - **/*.properties - - - - src/main/resources - false - - **/*.properties - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-jar-plugin - 2.3.1 - - - false - - com.skcraft.launcher.Launcher - - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.1 - - - package - - shade - - - - - org.projectlombok:lombok - - - - - - - false - - - - - - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..2cff48c --- /dev/null +++ b/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'launcher-parent' + +include 'launcher', 'launcher-builder' \ No newline at end of file diff --git a/src/main/java/com/skcraft/concurrency/DefaultProgress.java b/src/main/java/com/skcraft/concurrency/DefaultProgress.java deleted file mode 100644 index fd8a681..0000000 --- a/src/main/java/com/skcraft/concurrency/DefaultProgress.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.concurrency; - -import lombok.Data; - -/** - * A simple default implementation of {@link com.skcraft.concurrency.ProgressObservable} - * with settable properties. - */ -@Data -public class DefaultProgress implements ProgressObservable { - - private String status; - private double progress = -1; - - public DefaultProgress() { - } - - public DefaultProgress(double progress, String status) { - this.progress = progress; - this.status = status; - } -} diff --git a/src/main/java/com/skcraft/concurrency/ObservableFuture.java b/src/main/java/com/skcraft/concurrency/ObservableFuture.java deleted file mode 100644 index 1458d17..0000000 --- a/src/main/java/com/skcraft/concurrency/ObservableFuture.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.concurrency; - -import com.google.common.util.concurrent.ListenableFuture; -import lombok.NonNull; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * A pair of ProgressObservable and ListenableFuture. - * - * @param the result type - */ -public class ObservableFuture implements ListenableFuture, ProgressObservable { - - private final ListenableFuture future; - private final ProgressObservable observable; - - /** - * Construct a new ObservableFuture. - * - * @param future the delegate future - * @param observable the observable - */ - public ObservableFuture(@NonNull ListenableFuture future, @NonNull ProgressObservable observable) { - this.future = future; - this.observable = observable; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return future.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return future.isCancelled(); - } - - @Override - public void addListener(Runnable listener, Executor executor) { - future.addListener(listener, executor); - } - - @Override - public boolean isDone() { - return future.isDone(); - } - - @Override - public V get() throws InterruptedException, ExecutionException { - return future.get(); - } - - @Override - public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return future.get(timeout, unit); - } - - @Override - public String toString() { - return observable.toString(); - } - - @Override - public double getProgress() { - return observable.getProgress(); - } - - @Override - public String getStatus() { - return observable.getStatus(); - } - -} diff --git a/src/main/java/com/skcraft/concurrency/ProgressFilter.java b/src/main/java/com/skcraft/concurrency/ProgressFilter.java deleted file mode 100644 index d9cf69e..0000000 --- a/src/main/java/com/skcraft/concurrency/ProgressFilter.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.concurrency; - -public class ProgressFilter implements ProgressObservable { - - private final ProgressObservable delegate; - private final double offset; - private final double portion; - - public ProgressFilter(ProgressObservable delegate, double offset, double portion) { - this.delegate = delegate; - this.offset = offset; - this.portion = portion; - } - - @Override - public double getProgress() { - return offset + portion * Math.max(0, delegate.getProgress()); - } - - @Override - public String getStatus() { - return delegate.getStatus(); - } - - public static ProgressObservable between(ProgressObservable delegate, double from, double to) { - return new ProgressFilter(delegate, from, to - from); - } - -} diff --git a/src/main/java/com/skcraft/concurrency/ProgressObservable.java b/src/main/java/com/skcraft/concurrency/ProgressObservable.java deleted file mode 100644 index 2179e6a..0000000 --- a/src/main/java/com/skcraft/concurrency/ProgressObservable.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.concurrency; - -/** - * Implementations of this interface can provide information on the progress - * of a task. - */ -public interface ProgressObservable { - - /** - * Get the progress value as a number between 0 and 1 (inclusive), or -1 - * if progress information is unavailable. - * - * @return the progress value - */ - double getProgress(); - - /** - * Get the current status text. - * - * @return the status text, or null if unavailable - */ - String getStatus(); - -} diff --git a/src/main/java/com/skcraft/launcher/AssetsRoot.java b/src/main/java/com/skcraft/launcher/AssetsRoot.java deleted file mode 100644 index 11b94a1..0000000 --- a/src/main/java/com/skcraft/launcher/AssetsRoot.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher; - -import com.google.common.io.Files; -import com.skcraft.concurrency.ProgressObservable; -import com.skcraft.launcher.model.minecraft.Asset; -import com.skcraft.launcher.model.minecraft.AssetsIndex; -import com.skcraft.launcher.model.minecraft.VersionManifest; -import com.skcraft.launcher.persistence.Persistence; -import lombok.Getter; -import lombok.NonNull; -import lombok.extern.java.Log; - -import java.io.File; -import java.io.IOException; -import java.util.Map; -import java.util.logging.Level; - -import static com.skcraft.launcher.util.SharedLocale._; - -/** - * Represents a directory that stores assets for Minecraft. The class has - * various methods that abstract operations involving the assets (such - * as getting the path to a certain object). - */ -@Log -public class AssetsRoot { - - @Getter - private final File dir; - - /** - * Create a new instance. - * - * @param dir the directory to the assets folder - */ - public AssetsRoot(@NonNull File dir) { - this.dir = dir; - } - - /** - * Get the path to the index .json file for a version manfiest. - * - * @param versionManifest the version manifest - * @return the file, which may not exist - */ - public File getIndexPath(VersionManifest versionManifest) { - return new File(dir, "indexes/" + versionManifest.getAssetsIndex() + ".json"); - } - - /** - * Get the local path for a given asset. - * - * @param asset the asset - * @return the file, which may not exist - */ - public File getObjectPath(Asset asset) { - String hash = asset.getHash(); - return new File(dir, "objects/" + hash.substring(0, 2) + "/" + hash); - } - - /** - * Create an instance of the assets tree builder, which copies the indexed - * assets (identified by hashes) into a directory where the assets - * have been renamed and moved to their real names and locations - * (i.e. sounds/whatever.ogg). - * - * @param versionManifest the version manifest - * @return the builder - * @throws LauncherException - */ - public AssetsTreeBuilder createAssetsBuilder(@NonNull VersionManifest versionManifest) throws LauncherException { - String indexId = versionManifest.getAssetsIndex(); - File path = getIndexPath(versionManifest); - AssetsIndex index = Persistence.read(path, AssetsIndex.class, true); - if (index == null || index.getObjects() == null) { - throw new LauncherException("Missing index at " + path, _("assets.missingIndex", path.getAbsolutePath())); - } - File treeDir = new File(dir, "virtual/" + indexId); - treeDir.mkdirs(); - return new AssetsTreeBuilder(index, treeDir); - } - - public class AssetsTreeBuilder implements ProgressObservable { - private final AssetsIndex index; - private final File destDir; - private final int count; - private int processed = 0; - - public AssetsTreeBuilder(AssetsIndex index, File destDir) { - this.index = index; - this.destDir = destDir; - count = index.getObjects().size(); - } - - public File build() throws IOException, LauncherException { - AssetsRoot.log.info("Building asset virtual tree at '" + destDir.getAbsolutePath() + "'..."); - - for (Map.Entry entry : index.getObjects().entrySet()) { - File objectPath = getObjectPath(entry.getValue()); - File virtualPath = new File(destDir, entry.getKey()); - virtualPath.getParentFile().mkdirs(); - if (!virtualPath.exists()) { - log.log(Level.INFO, "Copying {0} to {1}...", new Object[] { - objectPath.getAbsolutePath(), virtualPath.getAbsolutePath()}); - - if (!objectPath.exists()) { - String message = _("assets.missingObject", objectPath.getAbsolutePath()); - throw new LauncherException("Missing object " + objectPath.getAbsolutePath(), message); - } - - Files.copy(objectPath, virtualPath); - } - processed++; - } - - return destDir; - } - - @Override - public double getProgress() { - if (count == 0) { - return -1; - } else { - return processed / (double) count; - } - } - - @Override - public String getStatus() { - if (count == 0) { - return _("assets.expanding1", count, count - processed); - } else { - return _("assets.expandingN", count, count - processed); - } - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/Configuration.java b/src/main/java/com/skcraft/launcher/Configuration.java deleted file mode 100644 index 8471e68..0000000 --- a/src/main/java/com/skcraft/launcher/Configuration.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; - -/** - * The configuration for the launcher. - *

- * Default values are stored as field values. Note that if a default - * value is changed after the launcher has been deployed, it may not take effect - * for users who have already used the launcher because the old default - * values would have been written to disk. - */ -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class Configuration { - - private boolean offlineEnabled = false; - private String jvmPath; - private String jvmArgs; - private int minMemory = 1024; - private int maxMemory = 1024; - private int permGen = 128; - private int windowWidth = 854; - private int widowHeight = 480; - private boolean proxyEnabled = false; - private String proxyHost = "localhost"; - private int proxyPort = 8080; - private String proxyUsername; - private String proxyPassword; - private String gameKey; - - @Override - public boolean equals(Object o) { - return super.equals(o); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - -} diff --git a/src/main/java/com/skcraft/launcher/Instance.java b/src/main/java/com/skcraft/launcher/Instance.java deleted file mode 100644 index 9fa4a02..0000000 --- a/src/main/java/com/skcraft/launcher/Instance.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.skcraft.launcher.launch.JavaProcessBuilder; -import com.skcraft.launcher.model.modpack.LaunchModifier; -import lombok.Data; - -import java.io.File; -import java.net.URL; -import java.util.Date; - -/** - * An instance is a profile that represents one particular installation - * of the game, with separate files and so on. - */ -@Data -public class Instance implements Comparable { - - private String title; - private String name; - private String version; - private boolean updatePending; - private boolean installed; - private Date lastAccessed; - @JsonProperty("launch") - private LaunchModifier launchModifier; - - @JsonIgnore private File dir; - @JsonIgnore private URL manifestURL; - @JsonIgnore private int priority; - @JsonIgnore private boolean selected; - @JsonIgnore private boolean local; - - /** - * Get the tile of the instance, which might be the same as the - * instance name if no title is set. - * - * @return a title - */ - public String getTitle() { - return title != null ? title : name; - } - - /** - * Update the given process builder with launch settings that are - * specific to this instance. - * - * @param builder the process builder - */ - public void modify(JavaProcessBuilder builder) { - if (launchModifier != null) { - launchModifier.modify(builder); - } - } - - /** - * Get the file for the directory where Minecraft's game files are - * stored, including user files (screenshots, etc.). - * - * @return the content directory, which may not exist - */ - @JsonIgnore - public File getContentDir() { - File dir = new File(this.dir, "minecraft"); - if (!dir.exists()) { - dir.mkdirs(); - } - return dir; - } - - /** - * Get the file for the package manifest. - * - * @return the manifest path, which may not exist - */ - @JsonIgnore - public File getManifestPath() { - return new File(dir, "manifest.json"); - } - - /** - * Get the file for the Minecraft version manfiest file. - * - * @return the version path, which may not exist - */ - @JsonIgnore - public File getVersionPath() { - return new File(dir, "version.json"); - } - - /** - * Get the file for the custom JAR file. - * - * @return the JAR file, which may not exist - */ - @JsonIgnore - public File getCustomJarPath() { - return new File(getContentDir(), "custom_jar.jar"); - } - - @Override - public String toString() { - return name; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object other) { - return super.equals(other); - } - - @Override - public int compareTo(Instance o) { - if (isLocal() && !o.isLocal()) { - return -1; - } else if (!isLocal() && o.isLocal()) { - return 1; - } else if (isLocal() && o.isLocal()) { - Date otherDate = o.getLastAccessed(); - - if (otherDate == null && lastAccessed == null) { - return 0; - } else if (otherDate == null) { - return -1; - } else if (lastAccessed == null) { - return 1; - } else { - return -lastAccessed.compareTo(otherDate); - } - } else { - if (priority > o.priority) { - return -1; - } else if (priority < o.priority) { - return 1; - } else { - return 0; - } - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/InstanceList.java b/src/main/java/com/skcraft/launcher/InstanceList.java deleted file mode 100644 index 69d53df..0000000 --- a/src/main/java/com/skcraft/launcher/InstanceList.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher; - -import com.skcraft.concurrency.DefaultProgress; -import com.skcraft.concurrency.ProgressObservable; -import com.skcraft.launcher.model.modpack.ManifestInfo; -import com.skcraft.launcher.model.modpack.PackageList; -import com.skcraft.launcher.persistence.Persistence; -import com.skcraft.launcher.util.HttpRequest; -import lombok.Getter; -import lombok.NonNull; -import lombok.extern.java.Log; -import org.apache.commons.io.filefilter.DirectoryFileFilter; - -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Callable; - -import static com.skcraft.launcher.LauncherUtils.concat; -import static com.skcraft.launcher.util.SharedLocale._; - -/** - * Stores the list of instances. - */ -@Log -public class InstanceList { - - private final Launcher launcher; - @Getter private final List instances = new ArrayList(); - - /** - * Create a new instance list. - * - * @param launcher the launcher - */ - public InstanceList(@NonNull Launcher launcher) { - this.launcher = launcher; - } - - /** - * Get the instance at a particular index. - * - * @param index the index - * @return the instance - */ - public synchronized Instance get(int index) { - return instances.get(index); - } - - /** - * Get the number of instances. - * - * @return the number of instances - */ - public synchronized int size() { - return instances.size(); - } - - /** - * Create a worker that loads the list of instances from disk and from - * the remote list of packages. - * - * @return the worker - */ - public Enumerator createEnumerator() { - return new Enumerator(); - } - - /** - * Get a list of selected instances. - * - * @return a list of instances - */ - public synchronized List getSelected() { - List selected = new ArrayList(); - for (Instance instance : instances) { - if (instance.isSelected()) { - selected.add(instance); - } - } - - return selected; - } - - /** - * Sort the list of instances. - */ - public synchronized void sort() { - Collections.sort(instances); - } - - public final class Enumerator implements Callable, ProgressObservable { - private ProgressObservable progress = new DefaultProgress(-1, null); - - private Enumerator() { - } - - @Override - public InstanceList call() throws Exception { - log.info("Enumerating instance list..."); - progress = new DefaultProgress(0, _("instanceLoader.loadingLocal")); - - List local = new ArrayList(); - List remote = new ArrayList(); - - File[] dirs = launcher.getInstancesDir().listFiles((FileFilter) DirectoryFileFilter.INSTANCE); - if (dirs != null) { - for (File dir : dirs) { - File file = new File(dir, "instance.json"); - Instance instance = Persistence.load(file, Instance.class); - instance.setDir(dir); - instance.setName(dir.getName()); - instance.setSelected(true); - instance.setLocal(true); - local.add(instance); - - log.info(instance.getName() + " local instance found at " + dir.getAbsolutePath()); - } - } - - progress = new DefaultProgress(0.3, _("instanceLoader.checkingRemote")); - - try { - URL packagesURL = launcher.getPackagesURL(); - - PackageList packages = HttpRequest - .get(packagesURL) - .execute() - .expectResponseCode(200) - .returnContent() - .asJson(PackageList.class); - - if (packages.getMinimumVersion() > Launcher.PROTOCOL_VERSION) { - throw new LauncherException("Update required", _("errors.updateRequiredError")); - } - - for (ManifestInfo manifest : packages.getPackages()) { - boolean foundLocal = false; - - for (Instance instance : local) { - if (instance.getName().equalsIgnoreCase(manifest.getName())) { - foundLocal = true; - - instance.setTitle(manifest.getTitle()); - instance.setPriority(manifest.getPriority()); - URL url = concat(packagesURL, manifest.getLocation()); - instance.setManifestURL(url); - - log.info("(" + instance.getName() + ").setManifestURL(" + url + ")"); - - // Check if an update is required - if (instance.getVersion() == null || !instance.getVersion().equals(manifest.getVersion())) { - instance.setUpdatePending(true); - instance.setVersion(manifest.getVersion()); - Persistence.commitAndForget(instance); - log.info(instance.getName() + " requires an update to " + manifest.getVersion()); - } - } - } - - if (!foundLocal) { - File dir = new File(launcher.getInstancesDir(), manifest.getName()); - File file = new File(dir, "instance.json"); - Instance instance = Persistence.load(file, Instance.class); - instance.setDir(dir); - instance.setTitle(manifest.getTitle()); - instance.setName(manifest.getName()); - instance.setVersion(manifest.getVersion()); - instance.setPriority(manifest.getPriority()); - instance.setSelected(false); - instance.setManifestURL(concat(packagesURL, manifest.getLocation())); - instance.setUpdatePending(true); - instance.setLocal(false); - remote.add(instance); - - log.info("Available remote instance: '" + instance.getName() + - "' at version " + instance.getVersion()); - } - } - } catch (IOException e) { - throw new IOException("The list of modpacks could not be downloaded.", e); - } finally { - synchronized (InstanceList.this) { - instances.clear(); - instances.addAll(local); - instances.addAll(remote); - - log.info(instances.size() + " instance(s) enumerated."); - } - } - - return InstanceList.this; - } - - @Override - public double getProgress() { - return -1; - } - - @Override - public String getStatus() { - return progress.getStatus(); - } - } -} diff --git a/src/main/java/com/skcraft/launcher/Launcher.java b/src/main/java/com/skcraft/launcher/Launcher.java deleted file mode 100644 index 3a3750e..0000000 --- a/src/main/java/com/skcraft/launcher/Launcher.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher; - -import com.beust.jcommander.JCommander; -import com.beust.jcommander.ParameterException; -import com.google.common.base.Strings; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import com.skcraft.launcher.auth.AccountList; -import com.skcraft.launcher.auth.LoginService; -import com.skcraft.launcher.auth.YggdrasilLoginService; -import com.skcraft.launcher.dialog.LauncherFrame; -import com.skcraft.launcher.model.minecraft.VersionManifest; -import com.skcraft.launcher.persistence.Persistence; -import com.skcraft.launcher.swing.SwingHelper; -import com.skcraft.launcher.util.HttpRequest; -import com.skcraft.launcher.util.SharedLocale; -import com.skcraft.launcher.util.SimpleLogFormatter; -import lombok.Getter; -import lombok.NonNull; -import lombok.extern.java.Log; -import org.apache.commons.io.FileUtils; - -import javax.swing.*; -import java.io.File; -import java.io.FileFilter; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URL; -import java.net.URLEncoder; -import java.util.Locale; -import java.util.Properties; -import java.util.concurrent.Executors; -import java.util.logging.Level; - -/** - * The main entry point for the launcher. - */ -@Log -public final class Launcher { - - public static final int PROTOCOL_VERSION = 2; - - @Getter - private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); - @Getter private final File baseDir; - @Getter private final Properties properties; - @Getter private final InstanceList instances; - @Getter private final Configuration config; - @Getter private final AccountList accounts; - @Getter private final AssetsRoot assets; - - /** - * Create a new launcher instance with the given base directory. - * - * @param baseDir the base directory - * @throws java.io.IOException on load error - */ - public Launcher(@NonNull File baseDir) throws IOException { - SharedLocale.loadBundle("com.skcraft.launcher.lang.Launcher", Locale.getDefault()); - - this.baseDir = baseDir; - this.properties = LauncherUtils.loadProperties(Launcher.class, - "launcher.properties", "com.skcraft.launcher.propertiesFile"); - this.instances = new InstanceList(this); - this.assets = new AssetsRoot(new File(baseDir, "assets")); - this.config = Persistence.load(new File(baseDir, "config.json"), Configuration.class); - this.accounts = Persistence.load(new File(baseDir, "accounts.dat"), AccountList.class); - - if (accounts.getSize() > 0) { - accounts.setSelectedItem(accounts.getElementAt(0)); - } - - executor.submit(new Runnable() { - @Override - public void run() { - cleanupExtractDir(); - } - }); - } - - /** - * Get the launcher version. - * - * @return the launcher version - */ - public String getVersion() { - String version = getProperties().getProperty("version"); - if (version.equals("${project.version}")) { - return "1.0.0-SNAPSHOT"; - } - return version; - } - - /** - * Get a login service. - * - * @return a login service - */ - public LoginService getLoginService() { - return new YggdrasilLoginService(HttpRequest.url(getProperties().getProperty("yggdrasilAuthUrl"))); - } - - /** - * Get the directory containing the instances. - * - * @return the instances dir - */ - public File getInstancesDir() { - return new File(getBaseDir(), "instances"); - } - - /** - * Get the directory to store temporary files. - * - * @return the temporary directory - */ - public File getTemporaryDir() { - return new File(getBaseDir(), "temp"); - } - - /** - * Get the directory to store temporary install files. - * - * @return the temporary install directory - */ - public File getInstallerDir() { - return new File(getTemporaryDir(), "install"); - } - - /** - * Get the directory to store temporarily extracted files. - * - * @return the directory - */ - private File getExtractDir() { - return new File(getTemporaryDir(), "extract"); - } - - /** - * Delete old extracted files. - */ - public void cleanupExtractDir() { - log.info("Cleaning up temporary extracted files directory..."); - - final long now = System.currentTimeMillis(); - - File[] dirs = getExtractDir().listFiles(new FileFilter() { - @Override - public boolean accept(File pathname) { - try { - long time = Long.parseLong(pathname.getName()); - return (now - time) > (1000 * 60 * 60); - } catch (NumberFormatException e) { - return false; - } - } - }); - - if (dirs != null) { - for (File dir : dirs) { - log.info("Removing " + dir.getAbsolutePath() + "..."); - try { - FileUtils.deleteDirectory(dir); - } catch (IOException e) { - log.log(Level.WARNING, "Failed to delete " + dir.getAbsolutePath(), e); - } - } - } - } - - /** - * Create a new temporary directory to extract files to. - * - * @return the directory path - */ - public File createExtractDir() { - File dir = new File(getExtractDir(), String.valueOf(System.currentTimeMillis())); - dir.mkdirs(); - log.info("Created temporary directory " + dir.getAbsolutePath()); - return dir; - } - - /** - * Get the directory to store the launcher binaries. - * - * @return the libraries directory - */ - public File getLauncherBinariesDir() { - return new File(getBaseDir(), "launcher"); - } - - /** - * Get the directory to store common data files. - * - * @return the common data directory - */ - public File getCommonDataDir() { - return getBaseDir(); - } - - /** - * Get the directory to store libraries. - * - * @return the libraries directory - */ - public File getLibrariesDir() { - return new File(getCommonDataDir(), "libraries"); - } - - /** - * Get the directory to store versions. - * - * @return the versions directory - */ - public File getVersionsDir() { - return new File(getCommonDataDir(), "versions"); - } - - /** - * Get the directory to store a version. - * - * @param version the version - * @return the directory - */ - public File getVersionDir(String version) { - return new File(getVersionsDir(), version); - } - - /** - * Get the path to the JAR for the given version manifest. - * - * @param versionManifest the version manifest - * @return the path - */ - public File getJarPath(VersionManifest versionManifest) { - return new File(getVersionDir(versionManifest.getId()), versionManifest.getId() + ".jar"); - } - - /** - * Get the news URL. - * - * @return the news URL - */ - public URL getNewsURL() { - try { - return HttpRequest.url( - String.format(getProperties().getProperty("newsUrl"), - URLEncoder.encode(getVersion(), "UTF-8"))); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - /** - * Get the packages URL. - * - * @return the packages URL - */ - public URL getPackagesURL() { - try { - String key = Strings.nullToEmpty(getConfig().getGameKey()); - return HttpRequest.url( - String.format(getProperties().getProperty("packageListUrl"), - URLEncoder.encode(key, "UTF-8"))); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - /** - * Convenient method to fetch a property. - * - * @param key the key - * @return the property - */ - public String prop(String key) { - return getProperties().getProperty(key); - } - - /** - * Convenient method to fetch a property. - * - * @param key the key - * @param args formatting arguments - * @return the property - */ - public String prop(String key, String... args) { - return String.format(getProperties().getProperty(key), (Object[]) args); - } - - /** - * Convenient method to fetch a property. - * - * @param key the key - * @return the property - */ - public URL propUrl(String key) { - return HttpRequest.url(prop(key)); - } - - /** - * Convenient method to fetch a property. - * - * @param key the key - * @param args formatting arguments - * @return the property - */ - public URL propUrl(String key, String... args) { - return HttpRequest.url(prop(key, args)); - } - - /** - * Bootstrap. - * - * @param args args - */ - public static void main(String[] args) { - SimpleLogFormatter.configureGlobalLogger(); - - LauncherArguments options = new LauncherArguments(); - try { - new JCommander(options, args); - } catch (ParameterException e) { - System.err.print(e.getMessage()); - System.exit(1); - return; - } - - Integer bsVersion = options.getBootstrapVersion(); - log.info(bsVersion != null ? "Bootstrap version " + bsVersion + " detected" : "Not bootstrapped"); - - File dir = options.getDir(); - if (dir != null) { - log.info("Using given base directory " + dir.getAbsolutePath()); - } else { - dir = new File("."); - log.info("Using current directory " + dir.getAbsolutePath()); - } - - final File baseDir = dir; - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - UIManager.getDefaults().put("SplitPane.border", BorderFactory.createEmptyBorder()); - Launcher launcher = new Launcher(baseDir); - new LauncherFrame(launcher).setVisible(true); - } catch (Throwable t) { - log.log(Level.WARNING, "Load failure", t); - SwingHelper.showErrorDialog(null, "Uh oh! The updater couldn't be opened because a " + - "problem was encountered.", "Launcher error", t); - } - } - }); - - } - -} diff --git a/src/main/java/com/skcraft/launcher/LauncherArguments.java b/src/main/java/com/skcraft/launcher/LauncherArguments.java deleted file mode 100644 index 2b340e0..0000000 --- a/src/main/java/com/skcraft/launcher/LauncherArguments.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher; - -import com.beust.jcommander.Parameter; -import lombok.Data; - -import java.io.File; - -/** - * The command line arguments that the launcher accepts. - */ -@Data -public class LauncherArguments { - - @Parameter(names = "--dir") - private File dir; - - @Parameter(names = "--bootstrap-version") - private Integer bootstrapVersion; - - @Parameter(names = "--portable") - private boolean portable; - -} diff --git a/src/main/java/com/skcraft/launcher/LauncherException.java b/src/main/java/com/skcraft/launcher/LauncherException.java deleted file mode 100644 index dea9d41..0000000 --- a/src/main/java/com/skcraft/launcher/LauncherException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher; - -/** - * A human-readable error wrapper. - */ -public class LauncherException extends Exception { - - private final String localizedMessage; - - public LauncherException(String message, String localizedMessage) { - super(message); - this.localizedMessage = localizedMessage; - } - - public LauncherException(Throwable cause, String localizedMessage) { - super(cause.getMessage(), cause); - this.localizedMessage = localizedMessage; - } - - @Override - public String getLocalizedMessage() { - return localizedMessage; - } -} diff --git a/src/main/java/com/skcraft/launcher/LauncherUtils.java b/src/main/java/com/skcraft/launcher/LauncherUtils.java deleted file mode 100644 index 0cf6e99..0000000 --- a/src/main/java/com/skcraft/launcher/LauncherUtils.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher; - -import com.google.common.io.Closer; -import lombok.extern.java.Log; - -import java.io.*; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.List; -import java.util.Properties; -import java.util.regex.Pattern; - -@Log -public final class LauncherUtils { - - private static final Pattern absoluteUrlPattern = Pattern.compile("^[A-Za-z0-9\\-]+://.*$"); - - private LauncherUtils() { - } - - public static String getStackTrace(Throwable t) { - Writer result = new StringWriter(); - PrintWriter printWriter = new PrintWriter(result); - t.printStackTrace(printWriter); - return result.toString(); - } - - public static void checkInterrupted() throws InterruptedException { - if (Thread.interrupted()) { - throw new InterruptedException(); - } - } - - public static Properties loadProperties(Class clazz, String name, String extraProperty) throws IOException { - Closer closer = Closer.create(); - Properties prop = new Properties(); - try { - InputStream in = closer.register(clazz.getResourceAsStream(name)); - prop.load(in); - String extraPath = System.getProperty(extraProperty); - if (extraPath != null) { - log.info("Loading extra properties for " + - clazz.getCanonicalName() + ":" + name + " from " + extraPath + "..."); - in = closer.register(new BufferedInputStream(closer.register(new FileInputStream(extraPath)))); - prop.load(in); - } - } finally { - closer.close(); - } - - return prop; - } - - public static URL concat(URL baseUrl, String url) throws MalformedURLException { - if (absoluteUrlPattern.matcher(url).matches()) { - return new URL(url); - } - - int lastSlash = baseUrl.toExternalForm().lastIndexOf("/"); - if (lastSlash == -1) { - return new URL(url); - } - - int firstSlash = url.indexOf("/"); - if (firstSlash == 0) { - boolean portSet = (baseUrl.getDefaultPort() == baseUrl.getPort() || - baseUrl.getPort() == -1); - String port = portSet ? "" : ":" + baseUrl.getPort(); - return new URL(baseUrl.getProtocol() + "://" + baseUrl.getHost() - + port + url); - } else { - return new URL(baseUrl.toExternalForm().substring(0, lastSlash + 1) + url); - } - } - - - - public static void interruptibleDelete(File file, List failures) throws IOException, InterruptedException { - checkInterrupted(); - - if (file.isDirectory()) { - File[] files = file.listFiles(); - - if (files == null) { - throw new IOException("Failed to list contents of " + file.getAbsolutePath()); - } - - for (File f : files) { - interruptibleDelete(f, failures); - } - - if (!file.delete()) { - log.warning("Failed to delete " + file.getAbsolutePath()); - failures.add(file); - } - } else { - if (!file.exists()) { - throw new FileNotFoundException("Does not exist: " + file); - } - - if (!file.delete()) { - log.warning("Failed to delete " + file.getAbsolutePath()); - failures.add(file); - } - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/auth/Account.java b/src/main/java/com/skcraft/launcher/auth/Account.java deleted file mode 100644 index e99bade..0000000 --- a/src/main/java/com/skcraft/launcher/auth/Account.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.auth; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.google.common.base.Strings; -import lombok.Data; -import lombok.NonNull; - -import java.util.Date; - -/** - * A user account that can be stored and loaded. - */ -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class Account implements Comparable { - - private String id; - private String password; - private Date lastUsed; - - /** - * Create a new account. - */ - public Account() { - } - - /** - * Create a new account with the given ID. - * - * @param id the ID - */ - public Account(String id) { - setId(id); - } - - /** - * Set the account's stored password, that may be stored to disk. - * - * @param password the password - */ - public void setPassword(String password) { - if (password != null && password.isEmpty()) { - password = null; - } - this.password = Strings.emptyToNull(password); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Account account = (Account) o; - - if (!id.equalsIgnoreCase(account.id)) return false; - - return true; - } - - @Override - public int hashCode() { - return id.toLowerCase().hashCode(); - } - - @Override - public int compareTo(@NonNull Account o) { - Date otherDate = o.getLastUsed(); - - if (otherDate == null && lastUsed == null) { - return 0; - } else if (otherDate == null) { - return -1; - } else if (lastUsed == null) { - return 1; - } else { - return -lastUsed.compareTo(otherDate); - } - } - - @Override - public String toString() { - return getId(); - } - -} diff --git a/src/main/java/com/skcraft/launcher/auth/AccountList.java b/src/main/java/com/skcraft/launcher/auth/AccountList.java deleted file mode 100644 index ddf9cb0..0000000 --- a/src/main/java/com/skcraft/launcher/auth/AccountList.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.auth; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.skcraft.launcher.persistence.Scrambled; -import lombok.Getter; -import lombok.NonNull; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -/** - * A list of accounts that can be stored to disk. - */ -@Scrambled("ACCOUNT_LIST") -@JsonIgnoreProperties(ignoreUnknown = true) -@JsonAutoDetect( - getterVisibility = JsonAutoDetect.Visibility.NONE, - setterVisibility = JsonAutoDetect.Visibility.NONE, - fieldVisibility = JsonAutoDetect.Visibility.NONE) -public class AccountList extends AbstractListModel implements ComboBoxModel { - - @JsonProperty - @Getter - private List accounts = new ArrayList(); - private transient Account selected; - - /** - * Add a new account. - * - *

If there is already an existing account with the same ID, then the - * new account will not be added.

- * - * @param account the account to add - */ - public synchronized void add(@NonNull Account account) { - if (!accounts.contains(account)) { - accounts.add(account); - Collections.sort(accounts); - fireContentsChanged(this, 0, accounts.size()); - } - } - - /** - * Remove an account. - * - * @param account the account - */ - public synchronized void remove(@NonNull Account account) { - Iterator it = accounts.iterator(); - while (it.hasNext()) { - Account other = it.next(); - if (other.equals(account)) { - it.remove(); - fireContentsChanged(this, 0, accounts.size() + 1); - break; - } - } - } - - /** - * Set the list of accounts. - * - * @param accounts the list of accounts - */ - public synchronized void setAccounts(@NonNull List accounts) { - this.accounts = accounts; - Collections.sort(accounts); - } - - @Override - @JsonIgnore - public synchronized int getSize() { - return accounts.size(); - } - - @Override - public synchronized Account getElementAt(int index) { - try { - return accounts.get(index); - } catch (IndexOutOfBoundsException e) { - return null; - } - } - - @Override - public void setSelectedItem(Object item) { - if (item == null) { - selected = null; - return; - } - - if (item instanceof Account) { - this.selected = (Account) item; - } else { - String id = String.valueOf(item).trim(); - Account account = new Account(id); - for (Account test : accounts) { - if (test.equals(account)) { - account = test; - break; - } - } - selected = account; - } - - if (selected.getId() == null || selected.getId().isEmpty()) { - selected = null; - } - } - - @Override - @JsonIgnore - public Account getSelectedItem() { - return selected; - } - - public synchronized void forgetPasswords() { - for (Account account : accounts) { - account.setPassword(null); - } - } -} diff --git a/src/main/java/com/skcraft/launcher/auth/AuthenticationException.java b/src/main/java/com/skcraft/launcher/auth/AuthenticationException.java deleted file mode 100644 index 4dc3f4f..0000000 --- a/src/main/java/com/skcraft/launcher/auth/AuthenticationException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.auth; - -import com.skcraft.launcher.LauncherException; - -/** - * Thrown on authentication error. - */ -public class AuthenticationException extends LauncherException { - - public AuthenticationException(String message, String localizedMessage) { - super(message, localizedMessage); - } - - public AuthenticationException(Throwable cause, String localizedMessage) { - super(cause, localizedMessage); - } -} diff --git a/src/main/java/com/skcraft/launcher/auth/LoginService.java b/src/main/java/com/skcraft/launcher/auth/LoginService.java deleted file mode 100644 index 60419ed..0000000 --- a/src/main/java/com/skcraft/launcher/auth/LoginService.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.auth; - -import java.io.IOException; -import java.util.List; - -/** - * A service for creating authenticated sessions. - */ -public interface LoginService { - - /** - * Attempt to login with the given details. - * - * @param agent the game to authenticate for, such as "Minecraft" - * @param id the login ID - * @param password the password - * @return a list of authenticated sessions, which corresponds to identities - * @throws IOException thrown on I/O error - * @throws InterruptedException thrown if interrupted - * @throws AuthenticationException thrown on an authentication error - */ - List login(String agent, String id, String password) - throws IOException, InterruptedException, AuthenticationException; - -} diff --git a/src/main/java/com/skcraft/launcher/auth/OfflineSession.java b/src/main/java/com/skcraft/launcher/auth/OfflineSession.java deleted file mode 100644 index 0431b2c..0000000 --- a/src/main/java/com/skcraft/launcher/auth/OfflineSession.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.auth; - -import lombok.Getter; -import lombok.NonNull; - -import java.util.Collections; -import java.util.Map; -import java.util.UUID; - -/** - * An offline session. - */ -public class OfflineSession implements Session { - - private static Map dummyProperties = Collections.emptyMap(); - - @Getter - private final String name; - - /** - * Create a new offline session using the given player name. - * - * @param name the player name - */ - public OfflineSession(@NonNull String name) { - this.name = name; - } - - @Override - public String getUuid() { - return (new UUID(0, 0)).toString(); - } - - @Override - public String getClientToken() { - return "0"; - } - - @Override - public String getAccessToken() { - return "0"; - } - - @Override - public Map getUserProperties() { - return dummyProperties; - } - - @Override - public String getSessionToken() { - return "-"; - } - - @Override - public UserType getUserType() { - return UserType.LEGACY; - } - - @Override - public boolean isOnline() { - return false; - } - -} diff --git a/src/main/java/com/skcraft/launcher/auth/Session.java b/src/main/java/com/skcraft/launcher/auth/Session.java deleted file mode 100644 index 95b3e58..0000000 --- a/src/main/java/com/skcraft/launcher/auth/Session.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.auth; - -import java.util.Map; - -/** - * Represents an authenticated (or virtual) session. - */ -public interface Session { - - /** - * Get the user's UUID. - * - * @return the user's UUID - */ - String getUuid(); - - /** - * Get the user's game username. - * - * @return the username - */ - String getName(); - - /** - * Get the client token. - * - * @return client token - */ - String getClientToken(); - - /** - * Get the access token. - * - * @return the access token - */ - String getAccessToken(); - - /** - * Get a map of user properties. - * - * @return the map of user properties - */ - Map getUserProperties(); - - /** - * Get the session token string, which is in the form of - * token:accessToken:uuid for authenticated players, and - * simply - for offline players. - * - * @return the session token - */ - String getSessionToken(); - - /** - * Get the user type. - * - * @return the user type - */ - UserType getUserType(); - - /** - * Return true if the user is in an online session. - * - * @return true if online - */ - boolean isOnline(); - -} diff --git a/src/main/java/com/skcraft/launcher/auth/UserType.java b/src/main/java/com/skcraft/launcher/auth/UserType.java deleted file mode 100644 index 7dfeff6..0000000 --- a/src/main/java/com/skcraft/launcher/auth/UserType.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.auth; - -/** - * Represents the type of user for the account. - */ -public enum UserType { - - /** - * Legacy accounts login with an account username. - */ - LEGACY, - /** - * Mojang accounts login with an email address. - */ - MOJANG; - - /** - * Return a lowercase version of the enum type. - * - * @return the lowercase name - */ - public String getName() { - return name().toLowerCase(); - } - - -} diff --git a/src/main/java/com/skcraft/launcher/auth/YggdrasilLoginService.java b/src/main/java/com/skcraft/launcher/auth/YggdrasilLoginService.java deleted file mode 100644 index b686953..0000000 --- a/src/main/java/com/skcraft/launcher/auth/YggdrasilLoginService.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.auth; - -import com.fasterxml.jackson.annotation.*; -import com.skcraft.launcher.util.HttpRequest; -import lombok.Data; -import lombok.NonNull; -import lombok.ToString; - -import java.io.IOException; -import java.net.URL; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Creates authenticated sessions using the Mojang Yggdrasil login protocol. - */ -public class YggdrasilLoginService implements LoginService { - - private final URL authUrl; - - /** - * Create a new login service with the given authentication URL. - * - * @param authUrl the authentication URL - */ - public YggdrasilLoginService(@NonNull URL authUrl) { - this.authUrl = authUrl; - } - - @Override - public List login(String agent, String id, String password) - throws IOException, InterruptedException, AuthenticationException { - Object payload = new AuthenticatePayload(new Agent(agent), id, password); - - HttpRequest request = HttpRequest - .post(authUrl) - .bodyJson(payload) - .execute(); - - if (request.getResponseCode() != 200) { - ErrorResponse error = request.returnContent().asJson(ErrorResponse.class); - throw new AuthenticationException(error.getErrorMessage(), error.getErrorMessage()); - } else { - AuthenticateResponse response = request.returnContent().asJson(AuthenticateResponse.class); - return response.getAvailableProfiles(); - } - } - - @Data - private static class Agent { - private final String name; - private final int version = 1; - } - - @Data - private static class AuthenticatePayload { - private final Agent agent; - private final String username; - private final String password; - } - - @Data - @JsonIgnoreProperties(ignoreUnknown = true) - private static class AuthenticateResponse { - private String accessToken; - private String clientToken; - @JsonManagedReference private List availableProfiles; - private Profile selectedProfile; - } - - @Data - private static class ErrorResponse { - private String error; - private String errorMessage; - private String cause; - } - - /** - * Return in the list of available profiles. - */ - @Data - @ToString(exclude = "response") - @JsonIgnoreProperties(ignoreUnknown = true) - private static class Profile implements Session { - @JsonProperty("id") private String uuid; - private String name; - private boolean legacy; - @JsonIgnore private final Map userProperties = Collections.emptyMap(); - @JsonBackReference private AuthenticateResponse response; - - @Override - @JsonIgnore - public String getSessionToken() { - return String.format("token:%s:%s", getAccessToken(), getUuid()); - } - - @Override - @JsonIgnore - public String getClientToken() { - return response.getClientToken(); - } - - @Override - @JsonIgnore - public String getAccessToken() { - return response.getAccessToken(); - } - - @Override - @JsonIgnore - public UserType getUserType() { - return legacy ? UserType.LEGACY : UserType.MOJANG; - } - - @Override - public boolean isOnline() { - return true; - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/BuilderConfig.java b/src/main/java/com/skcraft/launcher/builder/BuilderConfig.java deleted file mode 100644 index 658d619..0000000 --- a/src/main/java/com/skcraft/launcher/builder/BuilderConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.skcraft.launcher.model.modpack.LaunchModifier; -import com.skcraft.launcher.model.modpack.Manifest; -import lombok.Data; - -import java.util.List; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.emptyToNull; - -@Data -public class BuilderConfig { - - private String name; - private String title; - private String gameVersion; - @JsonProperty("launch") - private LaunchModifier launchModifier; - private List features; - private FnPatternList userFiles; - - public void update(Manifest manifest) { - manifest.updateName(getName()); - manifest.updateTitle(getTitle()); - manifest.updateGameVersion(getGameVersion()); - manifest.setLaunchModifier(getLaunchModifier()); - } - - public void registerProperties(PropertiesApplicator applicator) { - if (features != null) { - for (FeaturePattern feature : features) { - checkNotNull(emptyToNull(feature.getFeature().getName()), - "Empty feature name found"); - applicator.register(feature); - } - } - - applicator.setUserFiles(userFiles); - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/BuilderOptions.java b/src/main/java/com/skcraft/launcher/builder/BuilderOptions.java deleted file mode 100644 index 793312e..0000000 --- a/src/main/java/com/skcraft/launcher/builder/BuilderOptions.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.beust.jcommander.Parameter; -import com.beust.jcommander.ParameterException; -import lombok.Data; - -import java.io.File; - -@Data -public class BuilderOptions { - - // Configuration - - // Override config - @Parameter(names = "--name") - private String name; - @Parameter(names = "--title") - private String title; - @Parameter(names = "--mc-version") - private String gameVersion; - - // Required - @Parameter(names = "--version", required = true) - private String version; - @Parameter(names = "--manifest-dest", required = true) - private File manifestPath; - - // Overall paths - @Parameter(names = {"--input", "-i"}) - private File inputPath; - @Parameter(names = {"--output", "-o"}) - private File outputPath; - - // Input paths - @Parameter(names = "--config") - private File configPath; - @Parameter(names = "--version-file") - private File versionManifestPath; - @Parameter(names = "--files") - private File filesDir; - @Parameter(names = "--loaders") - private File loadersDir; - - // Output paths - @Parameter(names = "--objects-dest") - private File objectsDir; - @Parameter(names = "--libraries-dest") - private File librariesDir; - - @Parameter(names = "--libs-url") - private String librariesLocation = "libraries"; - @Parameter(names = "--objects-url") - private String objectsLocation = "objects"; - - // Misc - @Parameter(names = "--pretty-print") - private boolean prettyPrinting; - - public void choosePaths() throws ParameterException { - if (configPath == null) { - requireInputPath("--config"); - configPath = new File(inputPath, "modpack.json"); - } - - if (versionManifestPath == null) { - requireInputPath("--version"); - versionManifestPath = new File(inputPath, "version.json"); - } - - if (filesDir == null) { - requireInputPath("--files"); - filesDir = new File(inputPath, "src"); - } - - if (loadersDir == null) { - requireInputPath("--loaders"); - loadersDir = new File(inputPath, "loaders"); - } - - if (objectsDir == null) { - requireOutputPath("--objects-dest"); - objectsDir = new File(outputPath, objectsLocation); - } - - if (librariesDir == null) { - requireOutputPath("--libs-dest"); - librariesDir = new File(outputPath, librariesLocation); - } - } - - private void requireOutputPath(String name) throws ParameterException { - if (outputPath == null) { - throw new ParameterException("Because " + name + " was not specified, --output needs to be specified as the output directory and then " + name + " will be default to a pre-set path within the output directory"); - } - } - - private void requireInputPath(String name) throws ParameterException { - if (inputPath == null) { - throw new ParameterException("Because " + name + " was not specified, --input needs to be specified as the project directory and then " + name + " will be default to a pre-set path within the project directory"); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java b/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java deleted file mode 100644 index 8f2e722..0000000 --- a/src/main/java/com/skcraft/launcher/builder/BuilderUtils.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.beust.jcommander.internal.Lists; -import org.apache.commons.compress.compressors.CompressorStreamFactory; - -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -public final class BuilderUtils { - - private BuilderUtils() { - } - - public static String normalizePath(String path) { - return path.replaceAll("^[/\\\\]*", "").replaceAll("[/\\\\]+", "/"); - } - - public static ZipEntry getZipEntry(ZipFile jarFile, String path) { - Enumeration entries = jarFile.entries(); - String expected = normalizePath(path); - - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - String test = normalizePath(entry.getName()); - if (expected.equals(test)) { - return entry; - } - } - - return null; - } - - public static List getCompressors(String repoUrl) { - if (repoUrl.matches("^https?://files.minecraftforge.net/maven/")) { - return Lists.newArrayList( - new Compressor("xz", CompressorStreamFactory.XZ), - new Compressor("pack", CompressorStreamFactory.PACK200)); - } else { - return Collections.emptyList(); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/ClientFileCollector.java b/src/main/java/com/skcraft/launcher/builder/ClientFileCollector.java deleted file mode 100644 index de8862a..0000000 --- a/src/main/java/com/skcraft/launcher/builder/ClientFileCollector.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; -import com.google.common.io.Files; -import com.skcraft.launcher.model.modpack.FileInstall; -import com.skcraft.launcher.model.modpack.Manifest; -import lombok.NonNull; -import lombok.extern.java.Log; -import org.apache.commons.io.FilenameUtils; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.Charset; - -/** - * Walks a path and adds hashed path versions to the given - * {@link com.skcraft.launcher.model.modpack.Manifest}. - */ -@Log -public class ClientFileCollector extends DirectoryWalker { - - public static final String URL_FILE_SUFFIX = ".url.txt"; - - private final Manifest manifest; - private final PropertiesApplicator applicator; - private final File destDir; - private HashFunction hf = Hashing.sha1(); - - /** - * Create a new collector. - * - * @param manifest the manifest - * @param applicator applies properties to manifest entries - * @param destDir the destination directory to copy the hashed objects - */ - public ClientFileCollector(@NonNull Manifest manifest, @NonNull PropertiesApplicator applicator, - @NonNull File destDir) { - this.manifest = manifest; - this.applicator = applicator; - this.destDir = destDir; - } - - @Override - protected DirectoryBehavior getBehavior(@NonNull String name) { - return getDirectoryBehavior(name); - } - - @Override - protected void onFile(File file, String relPath) throws IOException { - if (file.getName().endsWith(FileInfoScanner.FILE_SUFFIX) || file.getName().endsWith(URL_FILE_SUFFIX)) { - return; - } - - // url.txt override file - File urlFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + URL_FILE_SUFFIX); - String to; - if (urlFile.exists()) { - to = Files.readFirstLine(urlFile, Charset.defaultCharset()); - } else { - to = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(relPath)); - } - - FileInstall entry = new FileInstall(); - String hash = Files.hash(file, hf).toString(); - String hashedPath = hash.substring(0, 2) + "/" + hash.substring(2, 4) + "/" + hash; - File destPath = new File(destDir, hashedPath); - entry.setHash(hash); - entry.setLocation(hashedPath); - entry.setTo(to); - entry.setSize(file.length()); - applicator.apply(entry); - destPath.getParentFile().mkdirs(); - ClientFileCollector.log.info(String.format("Adding %s from %s...", relPath, file.getAbsolutePath())); - Files.copy(file, destPath); - manifest.getTasks().add(entry); - } - - public static DirectoryBehavior getDirectoryBehavior(@NonNull String name) { - if (name.startsWith(".")) { - return DirectoryBehavior.SKIP; - } else if (name.equals("_OPTIONAL")) { - return DirectoryBehavior.IGNORE; - } else if (name.equals("_SERVER")) { - return DirectoryBehavior.SKIP; - } else if (name.equals("_CLIENT")) { - return DirectoryBehavior.IGNORE; - } else { - return DirectoryBehavior.CONTINUE; - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/Compressor.java b/src/main/java/com/skcraft/launcher/builder/Compressor.java deleted file mode 100644 index 40958b1..0000000 --- a/src/main/java/com/skcraft/launcher/builder/Compressor.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import org.apache.commons.compress.compressors.CompressorException; -import org.apache.commons.compress.compressors.CompressorStreamFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public class Compressor { - - private static final CompressorStreamFactory factory = new CompressorStreamFactory(); - - private final String extension; - private final String format; - - public Compressor(String extension, String format) { - this.extension = extension; - this.format = format; - } - - public String transformPathname(String filename) { - return filename + "." + extension; - } - - public InputStream createInputStream(InputStream inputStream) throws IOException { - try { - return factory.createCompressorInputStream(format, inputStream); - } catch (CompressorException e) { - throw new IOException("Failed to create decompressor", e); - } - } - - public OutputStream createOutputStream(OutputStream outputStream) throws IOException { - try { - return factory.createCompressorOutputStream(format, outputStream); - } catch (CompressorException e) { - throw new IOException("Failed to create compressor", e); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/DirectoryWalker.java b/src/main/java/com/skcraft/launcher/builder/DirectoryWalker.java deleted file mode 100644 index f92726c..0000000 --- a/src/main/java/com/skcraft/launcher/builder/DirectoryWalker.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import lombok.NonNull; - -import java.io.File; -import java.io.IOException; - -/** - * Abstract class to recursively walk a directory, keep track of a relative - * path (which may be modified by dropping certain directory entries), - * and call {@link #onFile(java.io.File, String)} with each file. - */ -public abstract class DirectoryWalker { - - public enum DirectoryBehavior { - /** - * Continue and add the given directory to the relative path. - */ - CONTINUE, - /** - * Continue but don't add the given directory to the relative path. - */ - IGNORE, - /** - * Don't walk this directory. - */ - SKIP - } - - /** - * Walk the given directory. - * - * @param dir the directory - * @throws IOException thrown on I/O error - */ - public final void walk(@NonNull File dir) throws IOException { - walk(dir, ""); - } - - /** - * Recursively walk the given directory and keep track of the relative path. - * - * @param dir the directory - * @param basePath the base path - * @throws IOException - */ - private void walk(@NonNull File dir, @NonNull String basePath) throws IOException { - if (!dir.isDirectory()) { - throw new IllegalArgumentException(dir.getAbsolutePath() + " is not a directory"); - } - - File[] files = dir.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory()) { - String newPath = basePath; - - switch (getBehavior(file.getName())) { - case CONTINUE: - newPath += file.getName() + "/"; - case IGNORE: - walk(file, newPath); - break; - case SKIP: break; - } - } else { - onFile(file, basePath + file.getName()); - } - } - } - } - - /** - * Return the behavior for the given directory name. - * - * @param name the directory name - * @return the behavor - */ - protected DirectoryBehavior getBehavior(String name) { - return DirectoryBehavior.CONTINUE; - } - - /** - * Callback on each file. - * - * @param file the file - * @param relPath the relative path - * @throws IOException thrown on I/O error - */ - protected abstract void onFile(File file, String relPath) throws IOException; - - -} diff --git a/src/main/java/com/skcraft/launcher/builder/FeaturePattern.java b/src/main/java/com/skcraft/launcher/builder/FeaturePattern.java deleted file mode 100644 index 01cbf93..0000000 --- a/src/main/java/com/skcraft/launcher/builder/FeaturePattern.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.skcraft.launcher.model.modpack.Feature; -import lombok.Data; - -@Data -public class FeaturePattern { - - @JsonProperty("properties") - private Feature feature; - @JsonProperty("files") - private FnPatternList filePatterns; - - public boolean matches(String path) { - return filePatterns != null && filePatterns.matches(path); - } -} diff --git a/src/main/java/com/skcraft/launcher/builder/FileInfo.java b/src/main/java/com/skcraft/launcher/builder/FileInfo.java deleted file mode 100644 index bea808a..0000000 --- a/src/main/java/com/skcraft/launcher/builder/FileInfo.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.skcraft.launcher.model.modpack.Feature; -import lombok.Data; - -@Data -public class FileInfo { - - private Feature feature; - -} diff --git a/src/main/java/com/skcraft/launcher/builder/FileInfoScanner.java b/src/main/java/com/skcraft/launcher/builder/FileInfoScanner.java deleted file mode 100644 index bd1c1b7..0000000 --- a/src/main/java/com/skcraft/launcher/builder/FileInfoScanner.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.skcraft.launcher.model.modpack.Feature; -import lombok.Getter; -import lombok.extern.java.Log; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.emptyToNull; -import static com.skcraft.launcher.builder.ClientFileCollector.getDirectoryBehavior; -import static org.apache.commons.io.FilenameUtils.*; - -@Log -public class FileInfoScanner extends DirectoryWalker { - - private static final EnumSet MATCH_FLAGS = EnumSet.of( - FnMatch.Flag.CASEFOLD, FnMatch.Flag.PERIOD, FnMatch.Flag.PATHNAME); - public static final String FILE_SUFFIX = ".info.json"; - - private final ObjectMapper mapper; - @Getter - private final List patterns = new ArrayList(); - - public FileInfoScanner(ObjectMapper mapper) { - this.mapper = mapper; - } - - @Override - protected DirectoryBehavior getBehavior(String name) { - return getDirectoryBehavior(name); - } - - @Override - protected void onFile(File file, String relPath) throws IOException { - if (file.getName().endsWith(FILE_SUFFIX)) { - String fnPattern = - separatorsToUnix(getPath(relPath)) + - getBaseName(getBaseName(file.getName())) + "*"; - - FileInfo info = mapper.readValue(file, FileInfo.class); - Feature feature = info.getFeature(); - - if (feature != null) { - checkNotNull(emptyToNull(feature.getName()), - "Empty component name found in " + file.getAbsolutePath()); - - List patterns = new ArrayList(); - patterns.add(fnPattern); - FnPatternList patternList = new FnPatternList(); - patternList.setInclude(patterns); - patternList.setFlags(MATCH_FLAGS); - FeaturePattern fp = new FeaturePattern(); - fp.setFeature(feature); - fp.setFilePatterns(patternList); - getPatterns().add(fp); - - FileInfoScanner.log.info("Found .info.json file at " + file.getAbsolutePath() + - ", with pattern " + fnPattern + ", and component " + feature); - } - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/FnMatch.java b/src/main/java/com/skcraft/launcher/builder/FnMatch.java deleted file mode 100644 index 9a65859..0000000 --- a/src/main/java/com/skcraft/launcher/builder/FnMatch.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ -/* $OpenBSD: fnmatch.c,v 1.13 2006/03/31 05:34:14 deraadt Exp $ */ - -package com.skcraft.launcher.builder; - -import java.util.EnumSet; - -/* - * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. - * Compares a filename or pathname to a pattern. - */ -public class FnMatch { - - public static enum Flag { - - /** Disable backslash escaping. */ - NOESCAPE, - /** Slash must be matched by slash. */ - PATHNAME, - /** Period must be matched by period. */ - PERIOD, - /** Ignore / after Imatch. */ - LEADING_DIR, - /** Case insensitive search. */ - CASEFOLD - } - private static final int RANGE_ERROR = -1; - private static final int RANGE_NOMATCH = 0; - - public static boolean fnmatch(String pattern, String string, EnumSet flags) { - return match(pattern, 0, string, 0, flags); - } - - public static boolean fnmatch(String pattern, String string, int stringPos, Flag flag) { - return match(pattern, 0, string, stringPos, EnumSet.of(flag)); - } - - public static boolean fnmatch(String pattern, String string, int stringPos) { - return match(pattern, 0, string, stringPos, EnumSet.noneOf(Flag.class)); - } - - public static boolean fnmatch(String pattern, String string) { - return fnmatch(pattern, string, 0); - } - - private static boolean match(String pattern, int patternPos, - String string, int stringPos, EnumSet flags) { - char c; - - while (true) { - if (patternPos >= pattern.length()) { - if (flags.contains(Flag.LEADING_DIR) && string.charAt(stringPos) == '/') { - return true; - } - return stringPos == string.length(); - } - c = pattern.charAt(patternPos++); - switch (c) { - case '?': - if (stringPos >= string.length()) { - return false; - } - if (string.charAt(stringPos) == '/' && flags.contains(Flag.PATHNAME)) { - return false; - } - if (hasLeadingPeriod(string, stringPos, flags)) { - return false; - } - ++stringPos; - continue; - case '*': - /* Collapse multiple stars. */ - while (patternPos < pattern.length() && - (c = pattern.charAt(patternPos)) == '*') { - patternPos++; - } - - if (hasLeadingPeriod(string, stringPos, flags)) { - return false; - } - - /* Optimize for pattern with * at end or before /. */ - if (patternPos == pattern.length()) { - if (flags.contains(Flag.PATHNAME)) { - return flags.contains(Flag.LEADING_DIR) || - string.indexOf('/', stringPos) == -1; - } - return true; - } else if (c == '/' && flags.contains(Flag.PATHNAME)) { - stringPos = string.indexOf('/', stringPos); - if (stringPos == -1) { - return false; - } - continue; - } - - /* General case, use recursion. */ - while (stringPos < string.length()) { - if (flags.contains(Flag.PERIOD)) { - flags = EnumSet.copyOf(flags); - flags.remove(Flag.PERIOD); - } - if (match(pattern, patternPos, string, stringPos, flags)) { - return true; - } - if (string.charAt(stringPos) == '/' && flags.contains(Flag.PATHNAME)) { - break; - } - ++stringPos; - } - return false; - - case '[': - if (stringPos >= string.length()) { - return false; - } - if (string.charAt(stringPos) == '/' && flags.contains(Flag.PATHNAME)) { - return false; - } - if (hasLeadingPeriod(string, stringPos, flags)) { - return false; - } - - int result = matchRange(pattern, patternPos, string.charAt(stringPos), flags); - if (result == RANGE_ERROR) /* not a good range, treat as normal text */ { - break; - } - - if (result == RANGE_NOMATCH) { - return false; - } - - patternPos = result; - ++stringPos; - continue; - - case '\\': - if (!flags.contains(Flag.NOESCAPE)) { - if (patternPos >= pattern.length()) { - c = '\\'; - } else { - c = pattern.charAt(patternPos++); - } - } - break; - } - - if (stringPos >= string.length()) { - return false; - } - if (c != string.charAt(stringPos) && - !(flags.contains(Flag.CASEFOLD) && - Character.toLowerCase(c) == Character.toLowerCase(string.charAt(stringPos)))) { - return false; - } - ++stringPos; - } - /* NOTREACHED */ - } - - private static boolean hasLeadingPeriod(String string, int stringPos, EnumSet flags) { - if (stringPos > string.length() - 1) - return false; - return (stringPos == 0 - || (flags.contains(Flag.PATHNAME) && string.charAt(stringPos - 1) == '/')) - && string.charAt(stringPos) == '.' && flags.contains(Flag.PERIOD); - } - - private static int matchRange(String pattern, int patternPos, char test, EnumSet flags) { - boolean negate, ok; - char c, c2; - - if (patternPos >= pattern.length()) { - return RANGE_ERROR; - } - - /* - * A bracket expression starting with an unquoted circumflex - * character produces unspecified results (IEEE 1003.2-1992, - * 3.13.2). This implementation treats it like '!', for - * consistency with the regular expression syntax. - * J.T. Conklin (conklin@ngai.kaleida.com) - */ - c = pattern.charAt(patternPos); - negate = c == '!' || c == '^'; - if (negate) { - ++patternPos; - } - - if (flags.contains(Flag.CASEFOLD)) { - test = Character.toLowerCase(test); - } - - /* - * A right bracket shall lose its special meaning and represent - * itself in a bracket expression if it occurs first in the list. - * -- POSIX.2 2.8.3.2 - */ - ok = false; - while (true) { - if (patternPos >= pattern.length()) { - return RANGE_ERROR; - } - - c = pattern.charAt(patternPos++); - if (c == ']') { - break; - } - - if (c == '\\' && !flags.contains(Flag.NOESCAPE)) { - c = pattern.charAt(patternPos++); - } - if (c == '/' && flags.contains(Flag.PATHNAME)) { - return RANGE_NOMATCH; - } - if (flags.contains(Flag.CASEFOLD)) { - c = Character.toLowerCase(c); - } - if (pattern.charAt(patternPos) == '-' && - patternPos + 1 < pattern.length() && - (c2 = pattern.charAt(patternPos + 1)) != ']') { - patternPos += 2; - if (c2 == '\\' && !flags.contains(Flag.NOESCAPE)) { - if (patternPos >= pattern.length()) { - return RANGE_ERROR; - } - c = pattern.charAt(patternPos++); - } - if (flags.contains(Flag.CASEFOLD)) { - c2 = Character.toLowerCase(c2); - } - if (c <= test && test <= c2) { - ok = true; - } - } else if (c == test) { - ok = true; - } - } - - return ok == negate ? RANGE_NOMATCH : patternPos; - } -} \ No newline at end of file diff --git a/src/main/java/com/skcraft/launcher/builder/FnPatternList.java b/src/main/java/com/skcraft/launcher/builder/FnPatternList.java deleted file mode 100644 index dd9c3b0..0000000 --- a/src/main/java/com/skcraft/launcher/builder/FnPatternList.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Data; -import lombok.Getter; -import lombok.Setter; - -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; - -@Data -public class FnPatternList { - - private static final EnumSet DEFAULT_FLAGS = EnumSet.of( - FnMatch.Flag.CASEFOLD, FnMatch.Flag.PERIOD); - - private List include; - private List exclude; - @Getter @Setter @JsonIgnore - private EnumSet flags = DEFAULT_FLAGS; - - public boolean matches(String path) { - return include != null && matches(path, include) && (exclude == null || !matches(path, exclude)); - } - - public boolean matches(String path, Collection patterns) { - for (String pattern : patterns) { - if (FnMatch.fnmatch(pattern, path, flags)) { - return true; - } - } - - return false; - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/JarFileFilter.java b/src/main/java/com/skcraft/launcher/builder/JarFileFilter.java deleted file mode 100644 index 31ca410..0000000 --- a/src/main/java/com/skcraft/launcher/builder/JarFileFilter.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import java.io.File; -import java.io.FileFilter; - -public class JarFileFilter implements FileFilter { - - @Override - public boolean accept(File pathname) { - return pathname.getName().toLowerCase().endsWith(".jar"); - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java b/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java deleted file mode 100644 index 303c63d..0000000 --- a/src/main/java/com/skcraft/launcher/builder/PackageBuilder.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.beust.jcommander.JCommander; -import com.beust.jcommander.ParameterException; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.google.common.io.ByteStreams; -import com.google.common.io.CharStreams; -import com.google.common.io.Closer; -import com.google.common.io.Files; -import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.LauncherUtils; -import com.skcraft.launcher.model.loader.InstallProfile; -import com.skcraft.launcher.model.minecraft.Library; -import com.skcraft.launcher.model.minecraft.VersionManifest; -import com.skcraft.launcher.model.modpack.Manifest; -import com.skcraft.launcher.util.Environment; -import com.skcraft.launcher.util.HttpRequest; -import com.skcraft.launcher.util.SimpleLogFormatter; -import lombok.Getter; -import lombok.NonNull; -import lombok.extern.java.Log; - -import java.io.*; -import java.net.URL; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Properties; -import java.util.jar.JarFile; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.emptyToNull; -import static com.skcraft.launcher.util.HttpRequest.url; - -/** - * Builds packages for the launcher. - */ -@Log -public class PackageBuilder { - - private static final Pattern TWEAK_CLASS_ARG = Pattern.compile("--tweakClass\\s+([^\\s]+)"); - - private final Properties properties; - private final ObjectMapper mapper; - private ObjectWriter writer; - private final Manifest manifest; - private final PropertiesApplicator applicator; - @Getter - private boolean prettyPrint = false; - private List loaderLibraries = Lists.newArrayList(); - private List mavenRepos; - - /** - * Create a new package builder. - * - * @param mapper the mapper - * @param manifest the manifest - */ - public PackageBuilder(@NonNull ObjectMapper mapper, @NonNull Manifest manifest) throws IOException { - this.properties = LauncherUtils.loadProperties(Launcher.class, - "launcher.properties", "com.skcraft.launcher.propertiesFile"); - - this.mapper = mapper; - this.manifest = manifest; - this.applicator = new PropertiesApplicator(manifest); - setPrettyPrint(false); // Set writer - - Closer closer = Closer.create(); - try { - mavenRepos = mapper.readValue(closer.register(Launcher.class.getResourceAsStream("maven_repos.json")), new TypeReference>() { - }); - } finally { - closer.close(); - } - } - - public void setPrettyPrint(boolean prettyPrint) { - if (prettyPrint) { - writer = mapper.writerWithDefaultPrettyPrinter(); - } else { - writer = mapper.writer(); - } - this.prettyPrint = prettyPrint; - } - - public void scan(File dir) throws IOException { - logSection("Scanning for .info.json files..."); - - FileInfoScanner scanner = new FileInfoScanner(mapper); - scanner.walk(dir); - for (FeaturePattern pattern : scanner.getPatterns()) { - applicator.register(pattern); - } - } - - public void addFiles(File dir, File destDir) throws IOException { - logSection("Adding files to modpack..."); - - ClientFileCollector collector = new ClientFileCollector(this.manifest, applicator, destDir); - collector.walk(dir); - } - - public void addLoaders(File dir, File librariesDir) { - logSection("Checking for mod loaders to install..."); - - LinkedHashSet collected = new LinkedHashSet(); - - File[] files = dir.listFiles(new JarFileFilter()); - if (files != null) { - for (File file : files) { - try { - processLoader(collected, file, librariesDir); - } catch (IOException e) { - log.log(Level.WARNING, "Failed to add the loader at " + file.getAbsolutePath(), e); - } - } - } - - this.loaderLibraries.addAll(collected); - - VersionManifest version = manifest.getVersionManifest(); - collected.addAll(version.getLibraries()); - version.setLibraries(collected); - } - - private void processLoader(LinkedHashSet loaderLibraries, File file, File librariesDir) throws IOException { - log.info("Installing " + file.getName() + "..."); - - JarFile jarFile = new JarFile(file); - Closer closer = Closer.create(); - - try { - ZipEntry profileEntry = BuilderUtils.getZipEntry(jarFile, "install_profile.json"); - - if (profileEntry != null) { - InputStream stream = jarFile.getInputStream(profileEntry); - - // Read file - String data = CharStreams.toString(closer.register(new InputStreamReader(stream))); - data = data.replaceAll(",\\s*\\}", "}"); // Fix issues with trailing commas - - InstallProfile profile = mapper.readValue(data, InstallProfile.class); - VersionManifest version = manifest.getVersionManifest(); - - // Copy tweak class arguments - String args = profile.getVersionInfo().getMinecraftArguments(); - if (args != null) { - String existingArgs = Strings.nullToEmpty(version.getMinecraftArguments()); - - Matcher m = TWEAK_CLASS_ARG.matcher(args); - while (m.find()) { - version.setMinecraftArguments(existingArgs + " " + m.group()); - log.info("Adding " + m.group() + " to launch arguments"); - } - } - - // Add libraries - List libraries = profile.getVersionInfo().getLibraries(); - if (libraries != null) { - for (Library library : libraries) { - if (!version.getLibraries().contains(library)) { - loaderLibraries.add(library); - } - } - } - - // Copy main class - String mainClass = profile.getVersionInfo().getMainClass(); - if (mainClass != null) { - version.setMainClass(mainClass); - log.info("Using " + mainClass + " as the main class"); - } - - // Extract the library - String filePath = profile.getInstallData().getFilePath(); - String libraryPath = profile.getInstallData().getPath(); - - if (filePath != null && libraryPath != null) { - ZipEntry libraryEntry = BuilderUtils.getZipEntry(jarFile, filePath); - - if (libraryEntry != null) { - Library library = new Library(); - library.setName(libraryPath); - File extractPath = new File(librariesDir, library.getPath(Environment.getInstance())); - Files.createParentDirs(extractPath); - ByteStreams.copy(closer.register(jarFile.getInputStream(libraryEntry)), Files.newOutputStreamSupplier(extractPath)); - } else { - log.warning("Could not find the file '" + filePath + "' in " + file.getAbsolutePath() + ", which means that this mod loader will not work correctly"); - } - } - } else { - log.warning("The file at " + file.getAbsolutePath() + " did not appear to have an " + - "install_profile.json file inside -- is it actually an installer for a mod loader?"); - } - } finally { - closer.close(); - jarFile.close(); - } - } - - public void downloadLibraries(File librariesDir) throws IOException, InterruptedException { - logSection("Downloading libraries..."); - - // TODO: Download libraries for different environments -- As of writing, this is not an issue - Environment env = Environment.getInstance(); - - for (Library library : loaderLibraries) { - File outputPath = new File(librariesDir, library.getPath(env)); - - if (!outputPath.exists()) { - Files.createParentDirs(outputPath); - boolean found = false; - - // Gather a list of repositories to download from - List sources = Lists.newArrayList(); - if (library.getBaseUrl() != null) { - sources.add(library.getBaseUrl()); - } - sources.addAll(mavenRepos); - - // Try each repository - for (String baseUrl : sources) { - String pathname = library.getPath(env); - - // Some repositories compress their files - List compressors = BuilderUtils.getCompressors(baseUrl); - for (Compressor compressor : Lists.reverse(compressors)) { - pathname = compressor.transformPathname(pathname); - } - - URL url = new URL(baseUrl + pathname); - File tempFile = File.createTempFile("launcherlib", null); - - try { - log.info("Downloading library " + library.getName() + " from " + url + "..."); - HttpRequest.get(url).execute().expectResponseCode(200).saveContent(tempFile); - } catch (IOException e) { - log.info("Could not get file from " + url + ": " + e.getMessage()); - continue; - } - - // Decompress (if needed) and write to file - Closer closer = Closer.create(); - InputStream inputStream = closer.register(new FileInputStream(tempFile)); - inputStream = closer.register(new BufferedInputStream(inputStream)); - for (Compressor compressor : compressors) { - inputStream = closer.register(compressor.createInputStream(inputStream)); - } - ByteStreams.copy(inputStream, closer.register(new FileOutputStream(outputPath))); - - tempFile.delete(); - - found = true; - break; - } - - if (!found) { - log.warning("!! Failed to download the library " + library.getName() + " -- this means your copy of the libraries will lack this file"); - } - } - } - } - - public void validateManifest() { - checkNotNull(emptyToNull(manifest.getName()), "Package name is not defined"); - checkNotNull(emptyToNull(manifest.getGameVersion()), "Game version is not defined"); - } - - public void readConfig(File path) throws IOException { - if (path != null) { - BuilderConfig config = read(path, BuilderConfig.class); - config.update(manifest); - config.registerProperties(applicator); - } - } - - public void readVersionManifest(File path) throws IOException, InterruptedException { - logSection("Reading version manifest..."); - - if (path.exists()) { - VersionManifest versionManifest = read(path, VersionManifest.class); - manifest.setVersionManifest(versionManifest); - - log.info("Loaded version manifest from " + path.getAbsolutePath()); - } else { - URL url = url(String.format( - properties.getProperty("versionManifestUrl"), - manifest.getGameVersion())); - - log.info("Fetching version manifest from " + url + "..."); - - manifest.setVersionManifest(HttpRequest - .get(url) - .execute() - .expectResponseCode(200) - .returnContent() - .asJson(VersionManifest.class)); - } - } - - public void writeManifest(@NonNull File path) throws IOException { - logSection("Writing manifest..."); - - manifest.setFeatures(applicator.getFeaturesInUse()); - VersionManifest versionManifest = manifest.getVersionManifest(); - if (versionManifest != null) { - versionManifest.setId(manifest.getGameVersion()); - } - validateManifest(); - path.getAbsoluteFile().getParentFile().mkdirs(); - writer.writeValue(path, manifest); - - log.info("Wrote manifest to " + path.getAbsolutePath()); - } - - private static BuilderOptions parseArgs(String[] args) { - BuilderOptions options = new BuilderOptions(); - new JCommander(options, args); - options.choosePaths(); - return options; - } - - private V read(File path, Class clazz) throws IOException { - try { - if (path == null) { - return clazz.newInstance(); - } else { - return mapper.readValue(path, clazz); - } - } catch (InstantiationException e) { - throw new IOException("Failed to create " + clazz.getCanonicalName(), e); - } catch (IllegalAccessException e) { - throw new IOException("Failed to create " + clazz.getCanonicalName(), e); - } - } - - /** - * Build a package given the arguments. - * - * @param args arguments - * @throws IOException thrown on I/O error - * @throws InterruptedException on interruption - */ - public static void main(String[] args) throws IOException, InterruptedException { - BuilderOptions options; - try { - options = parseArgs(args); - } catch (ParameterException e) { - new JCommander().usage(); - System.err.println("error: " + e.getMessage()); - System.exit(1); - return; - } - - // Initialize - SimpleLogFormatter.configureGlobalLogger(); - ObjectMapper mapper = new ObjectMapper(); - mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); - - Manifest manifest = new Manifest(); - manifest.setMinimumVersion(Manifest.MIN_PROTOCOL_VERSION); - PackageBuilder builder = new PackageBuilder(mapper, manifest); - builder.setPrettyPrint(options.isPrettyPrinting()); - - // From config - builder.readConfig(options.getConfigPath()); - builder.readVersionManifest(options.getVersionManifestPath()); - - // From options - manifest.updateName(options.getName()); - manifest.updateTitle(options.getTitle()); - manifest.updateGameVersion(options.getGameVersion()); - manifest.setVersion(options.getVersion()); - manifest.setLibrariesLocation(options.getLibrariesLocation()); - manifest.setObjectsLocation(options.getObjectsLocation()); - - builder.scan(options.getFilesDir()); - builder.addFiles(options.getFilesDir(), options.getObjectsDir()); - builder.addLoaders(options.getLoadersDir(), options.getLibrariesDir()); - builder.downloadLibraries(options.getLibrariesDir()); - builder.writeManifest(options.getManifestPath()); - - logSection("Done"); - - log.info("Now upload the contents of " + options.getOutputPath() + " to your web server or CDN!"); - } - - private static void logSection(String name) { - log.info(""); - log.info("--- " + name + " ---"); - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/PropertiesApplicator.java b/src/main/java/com/skcraft/launcher/builder/PropertiesApplicator.java deleted file mode 100644 index 586a04b..0000000 --- a/src/main/java/com/skcraft/launcher/builder/PropertiesApplicator.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.skcraft.launcher.model.modpack.*; -import lombok.Getter; -import lombok.Setter; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -public class PropertiesApplicator { - - private final Manifest manifest; - private final Set used = new HashSet(); - private final List features = new ArrayList(); - @Getter @Setter - private FnPatternList userFiles; - - public PropertiesApplicator(Manifest manifest) { - this.manifest = manifest; - } - - public void apply(ManifestEntry entry) { - if (entry instanceof FileInstall) { - apply((FileInstall) entry); - } - } - - private void apply(FileInstall entry) { - String path = entry.getTargetPath(); - entry.setWhen(fromFeature(path)); - entry.setUserFile(isUserFile(path)); - } - - public boolean isUserFile(String path) { - if (userFiles != null) { - return userFiles.matches(path); - } else { - return false; - } - } - - public Condition fromFeature(String path) { - List found = new ArrayList(); - for (FeaturePattern pattern : features) { - if (pattern.matches(path)) { - used.add(pattern.getFeature()); - found.add(pattern.getFeature()); - } - } - - if (!found.isEmpty()) { - return new RequireAny(found); - } else { - return null; - } - } - - public void register(FeaturePattern component) { - features.add(component); - } - - public List getFeaturesInUse() { - return new ArrayList(used); - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/ServerCopyExport.java b/src/main/java/com/skcraft/launcher/builder/ServerCopyExport.java deleted file mode 100644 index fa3da44..0000000 --- a/src/main/java/com/skcraft/launcher/builder/ServerCopyExport.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.beust.jcommander.JCommander; -import com.google.common.io.Files; -import com.skcraft.launcher.util.SimpleLogFormatter; -import lombok.NonNull; -import lombok.extern.java.Log; - -import java.io.File; -import java.io.IOException; - -@Log -public class ServerCopyExport extends DirectoryWalker { - - private final File destDir; - - public ServerCopyExport(@NonNull File destDir) { - this.destDir = destDir; - } - - @Override - protected DirectoryBehavior getBehavior(String name) { - if (name.startsWith(".")) { - return DirectoryBehavior.SKIP; - } else if (name.equals("_SERVER")) { - return DirectoryBehavior.IGNORE; - } else if (name.equals("_CLIENT")) { - return DirectoryBehavior.SKIP; - } else { - return DirectoryBehavior.CONTINUE; - } - } - - @Override - protected void onFile(File file, String relPath) throws IOException { - File dest = new File(destDir, relPath); - - log.info("Copying " + file.getAbsolutePath() + " to " + dest.getAbsolutePath()); - dest.getParentFile().mkdirs(); - Files.copy(file, dest); - } - - public static void main(String[] args) throws IOException { - SimpleLogFormatter.configureGlobalLogger(); - - ServerExportOptions options = new ServerExportOptions(); - new JCommander(options, args); - - log.info("From: " + options.getSourceDir().getAbsolutePath()); - log.info("To: " + options.getDestDir().getAbsolutePath()); - ServerCopyExport task = new ServerCopyExport(options.getDestDir()); - task.walk(options.getSourceDir()); - } - -} diff --git a/src/main/java/com/skcraft/launcher/builder/ServerExportOptions.java b/src/main/java/com/skcraft/launcher/builder/ServerExportOptions.java deleted file mode 100644 index 2e243f4..0000000 --- a/src/main/java/com/skcraft/launcher/builder/ServerExportOptions.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.builder; - -import com.beust.jcommander.Parameter; -import lombok.Data; - -import java.io.File; - -@Data -public class ServerExportOptions { - - @Parameter(names = "--source", required = true) - private File sourceDir; - @Parameter(names = "--dest", required = true) - private File destDir; - -} diff --git a/src/main/java/com/skcraft/launcher/dialog/ConfigurationDialog.java b/src/main/java/com/skcraft/launcher/dialog/ConfigurationDialog.java deleted file mode 100644 index a08b05e..0000000 --- a/src/main/java/com/skcraft/launcher/dialog/ConfigurationDialog.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.dialog; - -import com.skcraft.launcher.Configuration; -import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.swing.*; -import com.skcraft.launcher.persistence.Persistence; -import lombok.NonNull; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import static com.skcraft.launcher.util.SharedLocale._; - -/** - * A dialog to modify configuration options. - */ -public class ConfigurationDialog extends JDialog { - - private final Configuration config; - private final ObjectSwingMapper mapper; - - private final JPanel tabContainer = new JPanel(new BorderLayout()); - private final JTabbedPane tabbedPane = new JTabbedPane(); - private final FormPanel javaSettingsPanel = new FormPanel(); - private final JTextField jvmPathText = new JTextField(); - private final JTextField jvmArgsText = new JTextField(); - private final JSpinner minMemorySpinner = new JSpinner(); - private final JSpinner maxMemorySpinner = new JSpinner(); - private final JSpinner permGenSpinner = new JSpinner(); - private final FormPanel gameSettingsPanel = new FormPanel(); - private final JSpinner widthSpinner = new JSpinner(); - private final JSpinner heightSpinner = new JSpinner(); - private final FormPanel proxySettingsPanel = new FormPanel(); - private final JCheckBox useProxyCheck = new JCheckBox(_("options.useProxyCheck")); - private final JTextField proxyHostText = new JTextField(); - private final JSpinner proxyPortText = new JSpinner(); - private final JTextField proxyUsernameText = new JTextField(); - private final JPasswordField proxyPasswordText = new JPasswordField(); - private final FormPanel advancedPanel = new FormPanel(); - private final JTextField gameKeyText = new JTextField(); - private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); - private final JButton okButton = new JButton(_("button.ok")); - private final JButton cancelButton = new JButton(_("button.cancel")); - private final JButton logButton = new JButton(_("options.launcherConsole")); - - /** - * Create a new configuration dialog. - * - * @param owner the window owner - * @param launcher the launcher - */ - public ConfigurationDialog(Window owner, @NonNull Launcher launcher) { - super(owner, ModalityType.DOCUMENT_MODAL); - - this.config = launcher.getConfig(); - mapper = new ObjectSwingMapper(config); - - setTitle(_("options.title")); - initComponents(); - setDefaultCloseOperation(DISPOSE_ON_CLOSE); - setSize(new Dimension(400, 500)); - setResizable(false); - setLocationRelativeTo(owner); - - mapper.map(jvmPathText, "jvmPath"); - mapper.map(jvmArgsText, "jvmArgs"); - mapper.map(minMemorySpinner, "minMemory"); - mapper.map(maxMemorySpinner, "maxMemory"); - mapper.map(permGenSpinner, "permGen"); - mapper.map(widthSpinner, "windowWidth"); - mapper.map(heightSpinner, "widowHeight"); - mapper.map(useProxyCheck, "proxyEnabled"); - mapper.map(proxyHostText, "proxyHost"); - mapper.map(proxyPortText, "proxyPort"); - mapper.map(proxyUsernameText, "proxyUsername"); - mapper.map(proxyPasswordText, "proxyPassword"); - mapper.map(gameKeyText, "gameKey"); - - mapper.copyFromObject(); - } - - private void initComponents() { - javaSettingsPanel.addRow(new JLabel(_("options.jvmPath")), jvmPathText); - javaSettingsPanel.addRow(new JLabel(_("options.jvmArguments")), jvmArgsText); - javaSettingsPanel.addRow(Box.createVerticalStrut(15)); - javaSettingsPanel.addRow(new JLabel(_("options.64BitJavaWarning"))); - javaSettingsPanel.addRow(new JLabel(_("options.minMemory")), minMemorySpinner); - javaSettingsPanel.addRow(new JLabel(_("options.maxMemory")), maxMemorySpinner); - javaSettingsPanel.addRow(new JLabel(_("options.permGen")), permGenSpinner); - SwingHelper.removeOpaqueness(javaSettingsPanel); - tabbedPane.addTab(_("options.javaTab"), SwingHelper.alignTabbedPane(javaSettingsPanel)); - - gameSettingsPanel.addRow(new JLabel(_("options.windowWidth")), widthSpinner); - gameSettingsPanel.addRow(new JLabel(_("options.windowHeight")), heightSpinner); - SwingHelper.removeOpaqueness(gameSettingsPanel); - tabbedPane.addTab(_("options.minecraftTab"), SwingHelper.alignTabbedPane(gameSettingsPanel)); - - proxySettingsPanel.addRow(useProxyCheck); - proxySettingsPanel.addRow(new JLabel(_("options.proxyHost")), proxyHostText); - proxySettingsPanel.addRow(new JLabel(_("options.proxyPort")), proxyPortText); - proxySettingsPanel.addRow(new JLabel(_("options.proxyUsername")), proxyUsernameText); - proxySettingsPanel.addRow(new JLabel(_("options.proxyPassword")), proxyPasswordText); - SwingHelper.removeOpaqueness(proxySettingsPanel); - tabbedPane.addTab(_("options.proxyTab"), SwingHelper.alignTabbedPane(proxySettingsPanel)); - - advancedPanel.addRow(new JLabel(_("options.gameKey")), gameKeyText); - SwingHelper.removeOpaqueness(advancedPanel); - tabbedPane.addTab(_("options.advancedTab"), SwingHelper.alignTabbedPane(advancedPanel)); - - buttonsPanel.addElement(logButton); - buttonsPanel.addGlue(); - buttonsPanel.addElement(okButton); - buttonsPanel.addElement(cancelButton); - - tabContainer.add(tabbedPane, BorderLayout.CENTER); - tabContainer.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - add(tabContainer, BorderLayout.CENTER); - add(buttonsPanel, BorderLayout.SOUTH); - - SwingHelper.equalWidth(okButton, cancelButton); - - cancelButton.addActionListener(ActionListeners.dispose(this)); - - okButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - save(); - } - }); - - logButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ConsoleFrame.showMessages(); - } - }); - } - - /** - * Save the configuration and close the dialog. - */ - public void save() { - mapper.copyFromSwing(); - Persistence.commitAndForget(config); - dispose(); - } -} diff --git a/src/main/java/com/skcraft/launcher/dialog/ConsoleFrame.java b/src/main/java/com/skcraft/launcher/dialog/ConsoleFrame.java deleted file mode 100644 index eb3125c..0000000 --- a/src/main/java/com/skcraft/launcher/dialog/ConsoleFrame.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.dialog; - -import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.swing.LinedBoxPanel; -import com.skcraft.launcher.swing.MessageLog; -import com.skcraft.launcher.swing.SwingHelper; -import com.skcraft.launcher.util.PastebinPoster; -import lombok.Getter; -import lombok.NonNull; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import static com.skcraft.launcher.util.SharedLocale._; - -/** - * A frame capable of showing messages. - */ -public class ConsoleFrame extends JFrame { - - private static ConsoleFrame globalFrame; - - @Getter private final Image trayRunningIcon; - @Getter private final Image trayClosedIcon; - - @Getter private final MessageLog messageLog; - @Getter private LinedBoxPanel buttonsPanel; - - private boolean registeredGlobalLog = false; - - /** - * Construct the frame. - * - * @param numLines number of lines to show at a time - * @param colorEnabled true to enable a colored console - */ - public ConsoleFrame(int numLines, boolean colorEnabled) { - this(_("console.title"), numLines, colorEnabled); - } - - /** - * Construct the frame. - * - * @param title the title of the window - * @param numLines number of lines to show at a time - * @param colorEnabled true to enable a colored console - */ - public ConsoleFrame(@NonNull String title, int numLines, boolean colorEnabled) { - messageLog = new MessageLog(numLines, colorEnabled); - trayRunningIcon = SwingHelper.readIconImage(Launcher.class, "tray_ok.png"); - trayClosedIcon = SwingHelper.readIconImage(Launcher.class, "tray_closed.png"); - - setTitle(title); - setIconImage(trayRunningIcon); - - setSize(new Dimension(650, 400)); - initComponents(); - - setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent event) { - performClose(); - } - }); - } - - /** - * Add components to the frame. - */ - private void initComponents() { - JButton pastebinButton = new JButton(_("console.uploadLog")); - buttonsPanel = new LinedBoxPanel(true); - - buttonsPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); - buttonsPanel.addElement(pastebinButton); - - add(buttonsPanel, BorderLayout.NORTH); - add(messageLog, BorderLayout.CENTER); - - pastebinButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - pastebinLog(); - } - }); - } - - /** - * Register the global logger if it hasn't been registered. - */ - private void registerLoggerHandler() { - if (!registeredGlobalLog) { - getMessageLog().registerLoggerHandler(); - registeredGlobalLog = true; - } - } - - /** - * Attempt to perform window close. - */ - protected void performClose() { - messageLog.detachGlobalHandler(); - messageLog.clear(); - registeredGlobalLog = false; - dispose(); - } - - /** - * Send the contents of the message log to a pastebin. - */ - private void pastebinLog() { - String text = messageLog.getPastableText(); - // Not really bytes! - messageLog.log(_("console.pasteUploading", text.length()), messageLog.asHighlighted()); - - PastebinPoster.paste(text, new PastebinPoster.PasteCallback() { - @Override - public void handleSuccess(String url) { - messageLog.log(_("console.pasteUploaded", url), messageLog.asHighlighted()); - SwingHelper.openURL(url, messageLog); - } - - @Override - public void handleError(String err) { - messageLog.log(_("console.pasteFailed", err), messageLog.asError()); - } - }); - } - - public static void showMessages() { - ConsoleFrame frame = globalFrame; - if (frame == null) { - frame = new ConsoleFrame(10000, false); - globalFrame = frame; - frame.setTitle(_("console.launcherConsoleTitle")); - frame.registerLoggerHandler(); - frame.setVisible(true); - } else { - frame.setVisible(true); - frame.registerLoggerHandler(); - frame.requestFocus(); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/dialog/FeatureSelectionDialog.java b/src/main/java/com/skcraft/launcher/dialog/FeatureSelectionDialog.java deleted file mode 100644 index 57b0150..0000000 --- a/src/main/java/com/skcraft/launcher/dialog/FeatureSelectionDialog.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.dialog; - -import com.skcraft.launcher.model.modpack.Feature; -import com.skcraft.launcher.swing.*; -import lombok.NonNull; - -import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import java.awt.*; -import java.util.List; - -import static com.skcraft.launcher.util.SharedLocale._; -import static javax.swing.BorderFactory.createEmptyBorder; - -public class FeatureSelectionDialog extends JDialog { - - private final List features; - private final JPanel container = new JPanel(new BorderLayout()); - private final JTextArea descText = new JTextArea(_("features.selectForInfo")); - private final JScrollPane descScroll = new JScrollPane(descText); - private final CheckboxTable componentsTable = new CheckboxTable(); - private final JScrollPane componentsScroll = new JScrollPane(componentsTable); - private final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, componentsScroll, descScroll); - private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); - private final JButton installButton = new JButton(_("features.install")); - - public FeatureSelectionDialog(Window owner, @NonNull List features) { - super(owner, ModalityType.DOCUMENT_MODAL); - - this.features = features; - - setTitle(_("features.title")); - initComponents(); - setDefaultCloseOperation(DISPOSE_ON_CLOSE); - setSize(new Dimension(500, 400)); - setResizable(false); - setLocationRelativeTo(owner); - } - - private void initComponents() { - componentsTable.setModel(new FeatureTableModel(features)); - - descScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - - descText.setFont(new JLabel().getFont()); - descText.setEditable(false); - descText.setWrapStyleWord(true); - descText.setLineWrap(true); - SwingHelper.removeOpaqueness(descText); - descText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); - - splitPane.setDividerLocation(300); - splitPane.setDividerSize(6); - SwingHelper.flattenJSplitPane(splitPane); - - container.setBorder(createEmptyBorder(12, 12, 12, 12)); - container.add(splitPane, BorderLayout.CENTER); - - buttonsPanel.addGlue(); - buttonsPanel.addElement(installButton); - - JLabel descLabel = new JLabel(_("features.intro")); - descLabel.setBorder(createEmptyBorder(12, 12, 4, 12)); - - SwingHelper.equalWidth(installButton, new JButton(_("button.cancel"))); - - add(descLabel, BorderLayout.NORTH); - add(container, BorderLayout.CENTER); - add(buttonsPanel, BorderLayout.SOUTH); - - componentsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - updateDescription(); - } - }); - - installButton.addActionListener(ActionListeners.dispose(this)); - } - - private void updateDescription() { - Feature feature = features.get(componentsTable.getSelectedRow()); - - if (feature != null) { - descText.setText(feature.getDescription()); - } else { - descText.setText(_("features.selectForInfo")); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/dialog/LauncherFrame.java b/src/main/java/com/skcraft/launcher/dialog/LauncherFrame.java deleted file mode 100644 index f40b40c..0000000 --- a/src/main/java/com/skcraft/launcher/dialog/LauncherFrame.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.dialog; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -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.Session; -import com.skcraft.launcher.launch.Runner; -import com.skcraft.launcher.launch.LaunchProcessHandler; -import com.skcraft.launcher.persistence.Persistence; -import com.skcraft.launcher.selfupdate.UpdateChecker; -import com.skcraft.launcher.selfupdate.SelfUpdater; -import com.skcraft.launcher.swing.*; -import com.skcraft.launcher.update.HardResetter; -import com.skcraft.launcher.update.Remover; -import com.skcraft.launcher.update.Updater; -import com.skcraft.launcher.util.SwingExecutor; -import lombok.NonNull; -import lombok.extern.java.Log; -import org.apache.commons.io.FileUtils; - -import javax.swing.*; -import javax.swing.event.TableModelEvent; -import javax.swing.event.TableModelListener; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseEvent; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.Date; -import java.util.logging.Level; - -import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor; -import static com.skcraft.launcher.util.SharedLocale._; - -/** - * The main launcher frame. - */ -@Log -public class LauncherFrame extends JFrame { - - private final Launcher launcher; - - private final HeaderPanel header = new HeaderPanel(); - private final InstanceTable instancesTable = new InstanceTable(); - private final InstanceTableModel instancesModel; - private final JScrollPane instanceScroll = new JScrollPane(instancesTable); - private WebpagePanel webView; - private JSplitPane splitPane; - private final JPanel container = new JPanel(); - private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true).fullyPadded(); - private final JButton launchButton = new JButton(_("launcher.launch")); - private final JButton refreshButton = new JButton(_("launcher.checkForUpdates")); - private final JButton optionsButton = new JButton(_("launcher.options")); - private final JButton selfUpdateButton = new JButton(_("launcher.updateLauncher")); - private final JCheckBox updateCheck = new JCheckBox(_("launcher.downloadUpdates")); - private URL updateUrl; - - /** - * Create a new frame. - * - * @param launcher the launcher - */ - public LauncherFrame(@NonNull Launcher launcher) { - super(_("launcher.title", launcher.getVersion())); - - this.launcher = launcher; - instancesModel = new InstanceTableModel(launcher.getInstances()); - - setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - setSize(700, 450); - setMinimumSize(new Dimension(400, 300)); - initComponents(); - setLocationRelativeTo(null); - - SwingHelper.setIconImage(this, Launcher.class, "icon.png"); - - loadInstances(); - checkLauncherUpdate(); - } - - private void initComponents() { - webView = WebpagePanel.forURL(launcher.getNewsURL(), false); - splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, instanceScroll, webView); - selfUpdateButton.setVisible(false); - - updateCheck.setSelected(true); - instancesTable.setModel(instancesModel); - launchButton.setFont(launchButton.getFont().deriveFont(Font.BOLD)); - splitPane.setDividerLocation(200); - splitPane.setDividerSize(4); - SwingHelper.flattenJSplitPane(splitPane); - buttonsPanel.addElement(refreshButton); - buttonsPanel.addElement(updateCheck); - buttonsPanel.addGlue(); - buttonsPanel.addElement(selfUpdateButton); - buttonsPanel.addElement(optionsButton); - buttonsPanel.addElement(launchButton); - container.setLayout(new BorderLayout()); - container.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10)); - container.add(splitPane, BorderLayout.CENTER); - add(buttonsPanel, BorderLayout.SOUTH); - add(container, BorderLayout.CENTER); - - instancesModel.addTableModelListener(new TableModelListener() { - @Override - public void tableChanged(TableModelEvent e) { - if (instancesTable.getRowCount() > 0) { - instancesTable.setRowSelectionInterval(0, 0); - } - } - }); - - instancesTable.addMouseListener(new DoubleClickToButtonAdapter(launchButton)); - - refreshButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - loadInstances(); - checkLauncherUpdate(); - } - }); - - selfUpdateButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - selfUpdate(); - } - }); - - optionsButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showOptions(); - } - }); - - launchButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - launch(); - } - }); - - instancesTable.addMouseListener(new PopupMouseAdapter() { - @Override - protected void showPopup(MouseEvent e) { - int index = instancesTable.rowAtPoint(e.getPoint()); - Instance selected = null; - if (index >= 0) { - instancesTable.setRowSelectionInterval(index, index); - selected = launcher.getInstances().get(index); - } - popupInstanceMenu(e.getComponent(), e.getX(), e.getY(), selected); - } - }); - } - - private void checkLauncherUpdate() { - if (SelfUpdater.updatedAlready) { - return; - } - - ListenableFuture future = launcher.getExecutor().submit(new UpdateChecker(launcher)); - - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(URL result) { - if (result != null) { - requestUpdate(result); - } - } - - @Override - public void onFailure(Throwable t) { - - } - }, SwingExecutor.INSTANCE); - } - - private void selfUpdate() { - URL url = updateUrl; - if (url != null) { - SelfUpdater downloader = new SelfUpdater(launcher, url); - ObservableFuture future = new ObservableFuture( - launcher.getExecutor().submit(downloader), downloader); - - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(File result) { - selfUpdateButton.setVisible(false); - SwingHelper.showMessageDialog( - LauncherFrame.this, - _("launcher.selfUpdateComplete"), - _("launcher.selfUpdateCompleteTitle"), - null, - JOptionPane.INFORMATION_MESSAGE); - } - - @Override - public void onFailure(Throwable t) { - } - }, SwingExecutor.INSTANCE); - - ProgressDialog.showProgress(this, future, _("launcher.selfUpdatingTitle"), _("launcher.selfUpdatingStatus")); - SwingHelper.addErrorDialogCallback(this, future); - } else { - selfUpdateButton.setVisible(false); - } - } - - private void requestUpdate(URL url) { - this.updateUrl = url; - selfUpdateButton.setVisible(true); - } - - /** - * Popup the menu for the instances. - * - * @param component the component - * @param x mouse X - * @param y mouse Y - * @param selected the selected instance, possibly null - */ - private void popupInstanceMenu(Component component, int x, int y, final Instance selected) { - JPopupMenu popup = new JPopupMenu(); - JMenuItem menuItem; - - if (selected != null) { - menuItem = new JMenuItem(!selected.isLocal() ? "Install" : "Launch"); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - launch(); - } - }); - popup.add(menuItem); - - if (selected.isLocal()) { - popup.addSeparator(); - - menuItem = new JMenuItem(_("instance.openFolder")); - menuItem.addActionListener(ActionListeners.browseDir( - LauncherFrame.this, selected.getContentDir(), true)); - popup.add(menuItem); - - menuItem = new JMenuItem(_("instance.openSaves")); - menuItem.addActionListener(ActionListeners.browseDir( - LauncherFrame.this, new File(selected.getContentDir(), "saves"), true)); - popup.add(menuItem); - - menuItem = new JMenuItem(_("instance.openResourcePacks")); - menuItem.addActionListener(ActionListeners.browseDir( - LauncherFrame.this, new File(selected.getContentDir(), "resourcepacks"), true)); - popup.add(menuItem); - - menuItem = new JMenuItem(_("instance.openScreenshots")); - menuItem.addActionListener(ActionListeners.browseDir( - LauncherFrame.this, new File(selected.getContentDir(), "screenshots"), true)); - popup.add(menuItem); - - menuItem = new JMenuItem(_("instance.copyAsPath")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - File dir = selected.getContentDir(); - dir.mkdirs(); - SwingHelper.setClipboard(dir.getAbsolutePath()); - } - }); - popup.add(menuItem); - - popup.addSeparator(); - - if (!selected.isUpdatePending()) { - menuItem = new JMenuItem(_("instance.forceUpdate")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - selected.setUpdatePending(true); - launch(); - instancesModel.update(); - } - }); - popup.add(menuItem); - } - - menuItem = new JMenuItem(_("instance.hardForceUpdate")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - confirmHardUpdate(selected); - } - }); - popup.add(menuItem); - - menuItem = new JMenuItem(_("instance.deleteFiles")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - confirmDelete(selected); - } - }); - popup.add(menuItem); - } - - popup.addSeparator(); - } - - menuItem = new JMenuItem(_("launcher.refreshList")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - loadInstances(); - } - }); - popup.add(menuItem); - - popup.show(component, x, y); - - } - - private void confirmDelete(Instance instance) { - if (!SwingHelper.confirmDialog(this, - _("instance.confirmDelete", instance.getTitle()), _("confirmTitle"))) { - return; - } - - // Execute the deleter - Remover resetter = new Remover(instance); - ObservableFuture future = new ObservableFuture( - launcher.getExecutor().submit(resetter), resetter); - - // Show progress - ProgressDialog.showProgress( - this, future, _("instance.deletingTitle"), _("instance.deletingStatus", instance.getTitle())); - SwingHelper.addErrorDialogCallback(this, future); - - // Update the list of instances after updating - future.addListener(new Runnable() { - @Override - public void run() { - loadInstances(); - } - }, SwingExecutor.INSTANCE); - } - - private void confirmHardUpdate(Instance instance) { - if (!SwingHelper.confirmDialog(this, _("instance.confirmHardUpdate"), _("confirmTitle"))) { - return; - } - - // Execute the resetter - HardResetter resetter = new HardResetter(instance); - ObservableFuture future = new ObservableFuture( - launcher.getExecutor().submit(resetter), resetter); - - // Show progress - ProgressDialog.showProgress( this, future, _("instance.resettingTitle"), - _("instance.resettingStatus", instance.getTitle())); - SwingHelper.addErrorDialogCallback(this, future); - - // Update the list of instances after updating - future.addListener(new Runnable() { - @Override - public void run() { - launch(); - instancesModel.update(); - } - }, SwingExecutor.INSTANCE); - } - - private void loadInstances() { - InstanceList.Enumerator loader = launcher.getInstances().createEnumerator(); - ObservableFuture future = new ObservableFuture( - launcher.getExecutor().submit(loader), loader); - - future.addListener(new Runnable() { - @Override - public void run() { - instancesModel.update(); - if (instancesTable.getRowCount() > 0) { - instancesTable.setRowSelectionInterval(0, 0); - } - requestFocus(); - } - }, SwingExecutor.INSTANCE); - - ProgressDialog.showProgress(this, future, _("launcher.checkingTitle"), _("launcher.checkingStatus")); - SwingHelper.addErrorDialogCallback(this, future); - } - - private void showOptions() { - ConfigurationDialog configDialog = new ConfigurationDialog(this, launcher); - configDialog.setVisible(true); - } - - private void launch() { - try { - final Instance instance = launcher.getInstances().get(instancesTable.getSelectedRow()); - boolean update = updateCheck.isSelected() && instance.isUpdatePending(); - - // Store last access date - Date now = new Date(); - instance.setLastAccessed(now); - Persistence.commitAndForget(instance); - - // Perform login - final Session session = LoginDialog.showLoginRequest(this, launcher); - if (session == null) { - return; - } - - // If we have to update, we have to update - if (!instance.isInstalled()) { - update = true; - } - - if (update) { - // Execute the updater - Updater updater = new Updater(launcher, instance); - updater.setOnline(session.isOnline()); - ObservableFuture future = new ObservableFuture( - launcher.getExecutor().submit(updater), updater); - - // Show progress - ProgressDialog.showProgress( - this, future, _("launcher.updatingTitle"), _("launcher.updatingStatus", instance.getTitle())); - SwingHelper.addErrorDialogCallback(this, future); - - // Update the list of instances after updating - future.addListener(new Runnable() { - @Override - public void run() { - instancesModel.update(); - } - }, SwingExecutor.INSTANCE); - - // On success, launch also - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(Instance result) { - launch(instance, session); - } - - @Override - public void onFailure(Throwable t) { - } - }, SwingExecutor.INSTANCE); - } else { - launch(instance, session); - } - } catch (ArrayIndexOutOfBoundsException e) { - SwingHelper.showErrorDialog(this, _("launcher.noInstanceError"), _("launcher.noInstanceTitle")); - } - } - - private void launch(Instance instance, Session session) { - final File extractDir = launcher.createExtractDir(); - - // Get the process - Runner task = new Runner(launcher, instance, session, extractDir); - ObservableFuture processFuture = new ObservableFuture( - launcher.getExecutor().submit(task), task); - - // Show process for the process retrieval - ProgressDialog.showProgress( - this, processFuture, _("launcher.launchingTItle"), _("launcher.launchingStatus", instance.getTitle())); - - // If the process is started, get rid of this window - Futures.addCallback(processFuture, new FutureCallback() { - @Override - public void onSuccess(Process result) { - dispose(); - } - - @Override - public void onFailure(Throwable t) { - } - }); - - // Watch the created process - ListenableFuture future = Futures.transform( - processFuture, new LaunchProcessHandler(launcher), launcher.getExecutor()); - SwingHelper.addErrorDialogCallback(null, future); - - // Clean up at the very end - future.addListener(new Runnable() { - @Override - public void run() { - try { - log.info("Process ended; cleaning up " + extractDir.getAbsolutePath()); - FileUtils.deleteDirectory(extractDir); - } catch (IOException e) { - log.log(Level.WARNING, "Failed to clean up " + extractDir.getAbsolutePath(), e); - } - instancesModel.update(); - } - }, sameThreadExecutor()); - } - -} diff --git a/src/main/java/com/skcraft/launcher/dialog/LoginDialog.java b/src/main/java/com/skcraft/launcher/dialog/LoginDialog.java deleted file mode 100644 index 1d206fa..0000000 --- a/src/main/java/com/skcraft/launcher/dialog/LoginDialog.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.dialog; - -import com.google.common.base.Strings; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.skcraft.concurrency.ObservableFuture; -import com.skcraft.concurrency.ProgressObservable; -import com.skcraft.launcher.Configuration; -import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.auth.*; -import com.skcraft.launcher.swing.*; -import com.skcraft.launcher.persistence.Persistence; -import com.skcraft.launcher.util.SwingExecutor; -import lombok.Getter; -import lombok.NonNull; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import java.io.IOException; -import java.util.Date; -import java.util.List; -import java.util.concurrent.Callable; - -import static com.skcraft.launcher.util.SharedLocale._; - -/** - * The login dialog. - */ -public class LoginDialog extends JDialog { - - private final Launcher launcher; - @Getter private final AccountList accounts; - @Getter private Session session; - - private final JComboBox idCombo = new JComboBox(); - private final JPasswordField passwordText = new JPasswordField(); - private final JCheckBox rememberIdCheck = new JCheckBox(_("login.rememberId")); - private final JCheckBox rememberPassCheck = new JCheckBox(_("login.rememberPassword")); - private final JButton loginButton = new JButton(_("login.login")); - private final LinkButton recoverButton = new LinkButton(_("login.recoverAccount")); - private final JButton offlineButton = new JButton(_("login.playOffline")); - private final JButton cancelButton = new JButton(_("button.cancel")); - private final FormPanel formPanel = new FormPanel(); - private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); - - /** - * Create a new login dialog. - * - * @param owner the owner - * @param launcher the launcher - */ - public LoginDialog(Window owner, @NonNull Launcher launcher) { - super(owner, ModalityType.DOCUMENT_MODAL); - - this.launcher = launcher; - this.accounts = launcher.getAccounts(); - - setTitle(_("login.title")); - initComponents(); - setDefaultCloseOperation(DISPOSE_ON_CLOSE); - setMinimumSize(new Dimension(420, 0)); - setResizable(false); - pack(); - setLocationRelativeTo(owner); - - setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent event) { - removeListeners(); - dispose(); - } - }); - } - - private void removeListeners() { - idCombo.setModel(new DefaultComboBoxModel()); - } - - private void initComponents() { - idCombo.setModel(getAccounts()); - updateSelection(); - - rememberIdCheck.setBorder(BorderFactory.createEmptyBorder()); - rememberPassCheck.setBorder(BorderFactory.createEmptyBorder()); - idCombo.setEditable(true); - idCombo.getEditor().selectAll(); - - loginButton.setFont(loginButton.getFont().deriveFont(Font.BOLD)); - - formPanel.addRow(new JLabel(_("login.idEmail")), idCombo); - formPanel.addRow(new JLabel(_("login.password")), passwordText); - formPanel.addRow(new JLabel(), rememberIdCheck); - formPanel.addRow(new JLabel(), rememberPassCheck); - buttonsPanel.setBorder(BorderFactory.createEmptyBorder(26, 13, 13, 13)); - - if (launcher.getConfig().isOfflineEnabled()) { - buttonsPanel.addElement(offlineButton); - buttonsPanel.addElement(Box.createHorizontalStrut(2)); - } - buttonsPanel.addElement(recoverButton); - buttonsPanel.addGlue(); - buttonsPanel.addElement(loginButton); - buttonsPanel.addElement(cancelButton); - - add(formPanel, BorderLayout.CENTER); - add(buttonsPanel, BorderLayout.SOUTH); - - getRootPane().setDefaultButton(loginButton); - - passwordText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); - - idCombo.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - updateSelection(); - } - }); - - idCombo.getEditor().getEditorComponent().addMouseListener(new PopupMouseAdapter() { - @Override - protected void showPopup(MouseEvent e) { - popupManageMenu(e.getComponent(), e.getX(), e.getY()); - } - }); - - recoverButton.addActionListener( - ActionListeners.openURL(recoverButton, launcher.getProperties().getProperty("resetPasswordUrl"))); - - loginButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - prepareLogin(); - } - }); - - offlineButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setResult(new OfflineSession(launcher.getProperties().getProperty("offlinePlayerName"))); - removeListeners(); - dispose(); - } - }); - - cancelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - removeListeners(); - dispose(); - } - }); - - rememberPassCheck.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (rememberPassCheck.isSelected()) { - rememberIdCheck.setSelected(true); - } - } - }); - - rememberIdCheck.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (!rememberIdCheck.isSelected()) { - rememberPassCheck.setSelected(false); - } - } - }); - } - - private void popupManageMenu(Component component, int x, int y) { - Object selected = idCombo.getSelectedItem(); - JPopupMenu popup = new JPopupMenu(); - JMenuItem menuItem; - - if (selected != null && selected instanceof Account) { - final Account account = (Account) selected; - - menuItem = new JMenuItem(_("login.forgetUser")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - accounts.remove(account); - Persistence.commitAndForget(accounts); - } - }); - popup.add(menuItem); - - if (!Strings.isNullOrEmpty(account.getPassword())) { - menuItem = new JMenuItem(_("login.forgetPassword")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - account.setPassword(null); - Persistence.commitAndForget(accounts); - } - }); - popup.add(menuItem); - } - } - - menuItem = new JMenuItem(_("login.forgetAllPasswords")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (SwingHelper.confirmDialog(LoginDialog.this, - _("login.confirmForgetAllPasswords"), - _("login.forgetAllPasswordsTitle"))) { - accounts.forgetPasswords(); - Persistence.commitAndForget(accounts); - } - } - }); - popup.add(menuItem); - - popup.show(component, x, y); - } - - private void updateSelection() { - Object selected = idCombo.getSelectedItem(); - - if (selected != null && selected instanceof Account) { - Account account = (Account) selected; - String password = account.getPassword(); - - rememberIdCheck.setSelected(true); - if (!Strings.isNullOrEmpty(password)) { - rememberPassCheck.setSelected(true); - passwordText.setText(password); - } else { - rememberPassCheck.setSelected(false); - } - } else { - passwordText.setText(""); - rememberIdCheck.setSelected(true); - rememberPassCheck.setSelected(false); - } - } - - @SuppressWarnings("deprecation") - private void prepareLogin() { - Object selected = idCombo.getSelectedItem(); - - if (selected != null && selected instanceof Account) { - Account account = (Account) selected; - String password = passwordText.getText(); - - if (password == null || password.isEmpty()) { - SwingHelper.showErrorDialog(this, _("login.noPasswordError"), _("login.noPasswordTitle")); - } else { - if (rememberPassCheck.isSelected()) { - account.setPassword(password); - } else { - account.setPassword(null); - } - - if (rememberIdCheck.isSelected()) { - accounts.add(account); - } else { - accounts.remove(account); - } - - account.setLastUsed(new Date()); - - Persistence.commitAndForget(accounts); - - attemptLogin(account, password); - } - } else { - SwingHelper.showErrorDialog(this, _("login.noLoginError"), _("login.noLoginTitle")); - } - } - - private void attemptLogin(Account account, String password) { - LoginCallable callable = new LoginCallable(account, password); - ObservableFuture future = new ObservableFuture( - launcher.getExecutor().submit(callable), callable); - - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(Session result) { - setResult(result); - } - - @Override - public void onFailure(Throwable t) { - } - }, SwingExecutor.INSTANCE); - - ProgressDialog.showProgress(this, future, _("login.loggingInTitle"), _("login.loggingInStatus")); - SwingHelper.addErrorDialogCallback(this, future); - } - - private void setResult(Session session) { - this.session = session; - removeListeners(); - dispose(); - } - - public static Session showLoginRequest(Window owner, Launcher launcher) { - LoginDialog dialog = new LoginDialog(owner, launcher); - dialog.setVisible(true); - return dialog.getSession(); - } - - private class LoginCallable implements Callable,ProgressObservable { - private final Account account; - private final String password; - - private LoginCallable(Account account, String password) { - this.account = account; - this.password = password; - } - - @Override - public Session call() throws AuthenticationException, IOException, InterruptedException { - LoginService service = launcher.getLoginService(); - List identities = service.login(launcher.getProperties().getProperty("agentName"), account.getId(), password); - - // The list of identities (profiles in Mojang terms) corresponds to whether the account - // owns the game, so we need to check that - if (identities.size() > 0) { - // Set offline enabled flag to true - Configuration config = launcher.getConfig(); - if (!config.isOfflineEnabled()) { - config.setOfflineEnabled(true); - Persistence.commitAndForget(config); - } - - Persistence.commitAndForget(getAccounts()); - return identities.get(0); - } else { - throw new AuthenticationException("Minecraft not owned", _("login.minecraftNotOwnedError")); - } - } - - @Override - public double getProgress() { - return -1; - } - - @Override - public String getStatus() { - return _("login.loggingInStatus"); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/dialog/ProcessConsoleFrame.java b/src/main/java/com/skcraft/launcher/dialog/ProcessConsoleFrame.java deleted file mode 100644 index 7f7ab6d..0000000 --- a/src/main/java/com/skcraft/launcher/dialog/ProcessConsoleFrame.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.dialog; - -import com.skcraft.launcher.swing.LinedBoxPanel; -import com.skcraft.launcher.swing.SwingHelper; -import lombok.Getter; -import lombok.Setter; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.PrintWriter; - -import static com.skcraft.launcher.util.SharedLocale._; - -/** - * A version of the console window that can manage a process. - */ -public class ProcessConsoleFrame extends ConsoleFrame { - - private JButton killButton; - private JButton minimizeButton; - private TrayIcon trayIcon; - - @Getter private Process process; - @Getter @Setter private boolean killOnClose; - - private PrintWriter processOut; - - /** - * Create a new instance of the frame. - * - * @param numLines the number of log lines - * @param colorEnabled whether color is enabled in the log - */ - public ProcessConsoleFrame(int numLines, boolean colorEnabled) { - super(_("console.title"), numLines, colorEnabled); - processOut = new PrintWriter( - getMessageLog().getOutputStream(new Color(0, 0, 255)), true); - initComponents(); - updateComponents(); - } - - /** - * Track the given process. - * - * @param process the process - */ - public synchronized void setProcess(Process process) { - try { - Process lastProcess = this.process; - if (lastProcess != null) { - processOut.println(_("console.processEndCode", lastProcess.exitValue())); - } - } catch (IllegalThreadStateException e) { - } - - if (process != null) { - processOut.println(_("console.attachedToProcess")); - } - - this.process = process; - - SwingUtilities.invokeLater(new Runnable() { - public void run() { - updateComponents(); - } - }); - } - - private synchronized boolean hasProcess() { - return process != null; - } - - @Override - protected void performClose() { - if (hasProcess()) { - if (killOnClose) { - performKill(); - } - } - - if (trayIcon != null) { - SystemTray.getSystemTray().remove(trayIcon); - } - - super.performClose(); - } - - private void performKill() { - if (!confirmKill()) { - return; - } - - synchronized (this) { - if (hasProcess()) { - process.destroy(); - setProcess(null); - } - } - - updateComponents(); - } - - protected void initComponents() { - killButton = new JButton(_("console.forceClose")); - minimizeButton = new JButton(); // Text set later - - LinedBoxPanel buttonsPanel = getButtonsPanel(); - buttonsPanel.addGlue(); - buttonsPanel.addElement(killButton); - buttonsPanel.addElement(minimizeButton); - - killButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - performKill(); - } - }); - - minimizeButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - contextualClose(); - } - }); - - if (!setupTrayIcon()) { - minimizeButton.setEnabled(true); - } - } - - private boolean setupTrayIcon() { - if (!SystemTray.isSupported()) { - return false; - } - - trayIcon = new TrayIcon(getTrayRunningIcon()); - trayIcon.setImageAutoSize(true); - trayIcon.setToolTip(_("console.trayTooltip")); - - trayIcon.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - reshow(); - } - }); - - PopupMenu popup = new PopupMenu(); - MenuItem item; - - popup.add(item = new MenuItem(_("console.trayTitle"))); - item.setEnabled(false); - - popup.add(item = new MenuItem(_("console.tray.showWindow"))); - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - reshow(); - } - }); - - popup.add(item = new MenuItem(_("console.tray.forceClose"))); - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - performKill(); - } - }); - - trayIcon.setPopupMenu(popup); - - try { - SystemTray tray = SystemTray.getSystemTray(); - tray.add(trayIcon); - return true; - } catch (AWTException e) { - } - - return false; - } - - private synchronized void updateComponents() { - Image icon = hasProcess() ? getTrayRunningIcon() : getTrayClosedIcon(); - - killButton.setEnabled(hasProcess()); - - if (!hasProcess() || trayIcon == null) { - minimizeButton.setText(_("console.closeWindow")); - } else { - minimizeButton.setText(_("console.hideWindow")); - } - - if (trayIcon != null) { - trayIcon.setImage(icon); - } - - setIconImage(icon); - } - - private synchronized void contextualClose() { - if (!hasProcess() || trayIcon == null) { - performClose(); - } else { - minimize(); - } - - updateComponents(); - } - - private boolean confirmKill() { - return SwingHelper.confirmDialog(this, _("console.confirmKill"), _("console.confirmKillTitle")); - } - - private void minimize() { - setVisible(false); - } - - private void reshow() { - setVisible(true); - requestFocus(); - } - -} diff --git a/src/main/java/com/skcraft/launcher/dialog/ProgressDialog.java b/src/main/java/com/skcraft/launcher/dialog/ProgressDialog.java deleted file mode 100644 index 4cb0320..0000000 --- a/src/main/java/com/skcraft/launcher/dialog/ProgressDialog.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.dialog; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.skcraft.concurrency.ObservableFuture; -import com.skcraft.concurrency.ProgressObservable; -import com.skcraft.launcher.swing.LinedBoxPanel; -import com.skcraft.launcher.swing.SwingHelper; -import com.skcraft.launcher.util.SwingExecutor; -import lombok.extern.java.Log; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.lang.ref.WeakReference; -import java.util.Timer; -import java.util.TimerTask; - -import static com.skcraft.launcher.util.SharedLocale._; - -@Log -public class ProgressDialog extends JDialog { - - private static WeakReference lastDialogRef; - - private final String defaultTitle; - private final String defaultMessage; - private final JLabel label = new JLabel(); - private final JPanel progressPanel = new JPanel(new BorderLayout(0, 5)); - private final JPanel textAreaPanel = new JPanel(new BorderLayout()); - private final JProgressBar progressBar = new JProgressBar(); - private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); - private final JTextArea logText = new JTextArea(); - private final JScrollPane logScroll = new JScrollPane(logText); - private final JButton detailsButton = new JButton(); - private final JButton logButton = new JButton(_("progress.viewLog")); - private final JButton cancelButton = new JButton(_("button.cancel")); - - public ProgressDialog(Window owner, String title, String message) { - super(owner, title, ModalityType.DOCUMENT_MODAL); - - setResizable(false); - initComponents(); - label.setText(message); - defaultTitle = title; - defaultMessage = message; - setCompactSize(); - setLocationRelativeTo(owner); - - setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent event) { - if (confirmCancel()) { - cancel(); - dispose(); - } - } - }); - } - - private void setCompactSize() { - detailsButton.setText(_("progress.details")); - logButton.setVisible(false); - setMinimumSize(new Dimension(400, 100)); - pack(); - } - - private void setDetailsSize() { - detailsButton.setText(_("progress.less")); - logButton.setVisible(true); - setSize(400, 350); - } - - private void initComponents() { - progressBar.setMaximum(1000); - progressBar.setMinimum(0); - progressBar.setIndeterminate(true); - progressBar.setPreferredSize(new Dimension(0, 18)); - - buttonsPanel.addElement(detailsButton); - buttonsPanel.addElement(logButton); - buttonsPanel.addGlue(); - buttonsPanel.addElement(cancelButton); - buttonsPanel.setBorder(BorderFactory.createEmptyBorder(30, 13, 13, 13)); - - logScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - logText.setBackground(getBackground()); - logText.setEditable(false); - logText.setLineWrap(true); - logText.setWrapStyleWord(false); - logText.setFont(new JLabel().getFont()); - - progressPanel.add(label, BorderLayout.NORTH); - progressPanel.setBorder(BorderFactory.createEmptyBorder(13, 13, 0, 13)); - progressPanel.add(progressBar, BorderLayout.CENTER); - textAreaPanel.setBorder(BorderFactory.createEmptyBorder(10, 13, 0, 13)); - textAreaPanel.add(logScroll, BorderLayout.CENTER); - - add(progressPanel, BorderLayout.NORTH); - add(textAreaPanel, BorderLayout.CENTER); - add(buttonsPanel, BorderLayout.SOUTH); - - textAreaPanel.setVisible(false); - cancelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (confirmCancel()) { - cancel(); - dispose(); - } - } - }); - - detailsButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - toggleDetails(); - } - }); - - logButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ConsoleFrame.showMessages(); - } - }); - } - - private boolean confirmCancel() { - return SwingHelper.confirmDialog(this, _("progress.confirmCancel"), _("progress.confirmCancelTitle")); - } - - protected void cancel() { - } - - private void toggleDetails() { - if (textAreaPanel.isVisible()) { - textAreaPanel.setVisible(false); - setCompactSize(); - } else { - textAreaPanel.setVisible(true); - setDetailsSize(); - } - setLocationRelativeTo(getOwner()); - } - - public static void showProgress(final Window owner, final ObservableFuture future, String title, String message) { - final ProgressDialog dialog = new ProgressDialog(owner, title, message) { - @Override - protected void cancel() { - future.cancel(true); - } - }; - - lastDialogRef = new WeakReference(dialog); - - final Timer timer = new Timer(); - timer.scheduleAtFixedRate(new UpdateProgress(dialog, future), 400, 400); - - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(Object result) { - timer.cancel(); - dialog.dispose(); - } - - @Override - public void onFailure(Throwable t) { - timer.cancel(); - dialog.dispose(); - } - }, SwingExecutor.INSTANCE); - - dialog.setVisible(true); - } - - public static ProgressDialog getLastDialog() { - WeakReference ref = lastDialogRef; - if (ref != null) { - return ref.get(); - } - - return null; - } - - private static class UpdateProgress extends TimerTask { - private final ProgressDialog dialog; - private final ProgressObservable observable; - - public UpdateProgress(ProgressDialog dialog, ProgressObservable observable) { - this.dialog = dialog; - this.observable = observable; - } - - @Override - public void run() { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - JProgressBar progressBar = dialog.progressBar; - JTextArea logText = dialog.logText; - JLabel label = dialog.label; - - double progress = observable.getProgress(); - if (progress >= 0) { - dialog.setTitle(_("progress.percentTitle", - Math.round(progress * 100 * 100) / 100.0, dialog.defaultTitle)); - progressBar.setValue((int) (progress * 1000)); - progressBar.setIndeterminate(false); - } else { - dialog.setTitle( dialog.defaultTitle); - progressBar.setIndeterminate(true); - } - - String status = observable.getStatus(); - if (status == null) { - status = _("progress.defaultStatus"); - label.setText(dialog.defaultMessage); - } else { - int index = status.indexOf('\n'); - if (index == -1) { - label.setText(status); - } else { - label.setText(status.substring(0, index)); - } - } - logText.setText(status); - logText.setCaretPosition(0); - } - }); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/install/Downloader.java b/src/main/java/com/skcraft/launcher/install/Downloader.java deleted file mode 100644 index 137daf1..0000000 --- a/src/main/java/com/skcraft/launcher/install/Downloader.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import com.skcraft.concurrency.ProgressObservable; - -import java.io.File; -import java.net.URL; -import java.util.List; - - -public interface Downloader extends ProgressObservable { - - File download(List urls, String key, long size, String name); - - File download(URL url, String key, long size, String name); -} diff --git a/src/main/java/com/skcraft/launcher/install/FeatureCache.java b/src/main/java/com/skcraft/launcher/install/FeatureCache.java deleted file mode 100644 index 052ed5d..0000000 --- a/src/main/java/com/skcraft/launcher/install/FeatureCache.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import lombok.Data; - -import java.util.HashMap; -import java.util.Map; - -@Data -public class FeatureCache { - - private Map selected = new HashMap(); - -} diff --git a/src/main/java/com/skcraft/launcher/install/FileCopy.java b/src/main/java/com/skcraft/launcher/install/FileCopy.java deleted file mode 100644 index 34064df..0000000 --- a/src/main/java/com/skcraft/launcher/install/FileCopy.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import com.google.common.io.Files; -import lombok.NonNull; -import lombok.extern.java.Log; - -import java.io.File; -import java.io.IOException; -import java.util.logging.Level; - -import static com.skcraft.launcher.util.SharedLocale._; - -@Log -public class FileCopy implements InstallTask { - - private final File from; - private final File to; - - public FileCopy(@NonNull File from, @NonNull File to) { - this.from = from; - this.to = to; - } - - @Override - public void execute() throws IOException { - log.log(Level.INFO, "Copying to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()}); - to.getParentFile().mkdirs(); - Files.copy(from, to); - } - - @Override - public double getProgress() { - return -1; - } - - @Override - public String getStatus() { - return _("installer.copyingFile", from, to); - } - -} diff --git a/src/main/java/com/skcraft/launcher/install/FileMover.java b/src/main/java/com/skcraft/launcher/install/FileMover.java deleted file mode 100644 index 3293eb6..0000000 --- a/src/main/java/com/skcraft/launcher/install/FileMover.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import lombok.NonNull; -import lombok.extern.java.Log; - -import java.io.File; -import java.io.IOException; -import java.util.logging.Level; - -import static com.skcraft.launcher.util.SharedLocale._; - -@Log -public class FileMover implements InstallTask { - - private final File from; - private final File to; - - public FileMover(@NonNull File from, @NonNull File to) { - this.from = from; - this.to = to; - } - - @Override - public void execute() throws IOException { - log.log(Level.INFO, "Moving to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()}); - to.getParentFile().mkdirs(); - to.delete(); - from.renameTo(to); - } - - @Override - public double getProgress() { - return -1; - } - - @Override - public String getStatus() { - return _("installer.movingFile", from, to); - } - -} diff --git a/src/main/java/com/skcraft/launcher/install/HttpDownloader.java b/src/main/java/com/skcraft/launcher/install/HttpDownloader.java deleted file mode 100644 index 840ede4..0000000 --- a/src/main/java/com/skcraft/launcher/install/HttpDownloader.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import com.google.common.base.Charsets; -import com.google.common.base.Strings; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import com.skcraft.concurrency.ProgressObservable; -import com.skcraft.launcher.util.HttpRequest; -import lombok.Getter; -import lombok.NonNull; -import lombok.Setter; -import lombok.extern.java.Log; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.*; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.logging.Level; - -import static com.skcraft.launcher.util.SharedLocale._; - -@Log -public class HttpDownloader implements Downloader { - - private final Random random = new Random(); - private final HashFunction hf = Hashing.sha1(); - - private final File tempDir; - @Getter @Setter private int threadCount = 6; - @Getter @Setter private int retryDelay = 2000; - @Getter @Setter private int tryCount = 3; - - private List queue = new ArrayList(); - private final Set usedKeys = new HashSet(); - - private final List running = new ArrayList(); - private final List failed = new ArrayList(); - private long downloaded = 0; - private long total = 0; - private int left = 0; - - /** - * Create a new downloader using the given executor. - * - * @param tempDir the temporary directory - */ - public HttpDownloader(@NonNull File tempDir) { - this.tempDir = tempDir; - } - - /** - * Make sure that we aren't re-using hash IDs. - * - * @param baseKey the key to make unique - * @return a unique key - */ - private String createUniqueKey(String baseKey) { - String key = baseKey; - int i = 0; - while (usedKeys.contains(key)) { - key = baseKey + "_" + (i++); - } - usedKeys.add(key); - return key; - } - - @Override - public synchronized File download(@NonNull List urls, @NonNull String key, long size, String name) { - if (urls.isEmpty()) { - throw new IllegalArgumentException("Can't download empty list of URLs"); - } - - String hash = hf.hashString(Strings.nullToEmpty(key) + urls.get(0), Charsets.UTF_8).toString(); - hash = createUniqueKey(hash); - File tempFile = new File(tempDir, hash.substring(0, 2) + "/" + hash); - - // If the file is already downloaded (such as from before), then don't re-download - if (!tempFile.exists()) { - total += size; - left++; - queue.add(new HttpDownloadJob(tempFile, urls, size, name != null ? name : tempFile.getName())); - } - - return tempFile; - } - - - @Override - public File download(URL url, String key, long size, String name) { - List urls = new ArrayList(); - urls.add(url); - return download(urls, key, size, name); - } - - /** - * Prevent further downloads from being queued and download queued files. - * - * @throws InterruptedException thrown on interruption - * @throws IOException thrown on I/O error - */ - public void execute() throws InterruptedException, IOException { - synchronized (this) { - queue = Collections.unmodifiableList(queue); - } - - ListeningExecutorService executor = MoreExecutors.listeningDecorator( - Executors.newFixedThreadPool(threadCount)); - - try { - List> futures = new ArrayList>(); - - synchronized (this) { - for (HttpDownloadJob job : queue) { - futures.add(executor.submit(job)); - } - } - - try { - Futures.allAsList(futures).get(); - } catch (ExecutionException e) { - throw new IOException("Something went wrong", e); - } - - synchronized (this) { - if (failed.size() > 0) { - throw new IOException(failed.size() + " file(s) could not be downloaded"); - } - } - } finally { - executor.shutdownNow(); - } - } - - @Override - public synchronized double getProgress() { - if (total <= 0) { - return -1; - } - - long downloaded = this.downloaded; - for (HttpDownloadJob job : running) { - downloaded += Math.max(0, job.getProgress() * job.size); - } - return downloaded / (double) total; - } - - @Override - public synchronized String getStatus() { - String failMessage = _("downloader.failedCount", failed.size()); - if (running.size() == 1) { - return _("downloader.downloadingItem", running.get(0).getName()) + - "\n" + running.get(0).getStatus() + - "\n" + failMessage; - } else if (running.size() > 0) { - StringBuilder builder = new StringBuilder(); - for (HttpDownloadJob job : running) { - builder.append("\n"); - builder.append(job.getStatus()); - } - return _("downloader.downloadingList", queue.size(), left, failed.size()) + - builder.toString() + - "\n" + failMessage; - } else { - return _("downloader.noDownloads"); - } - } - - public class HttpDownloadJob implements Runnable, ProgressObservable { - private final File destFile; - private final List urls; - private final long size; - @Getter private String name; - private HttpRequest request; - - private HttpDownloadJob(File destFile, List urls, long size, String name) { - this.destFile = destFile; - this.urls = urls; - this.size = size; - this.name = name; - } - - @Override - public void run() { - try { - synchronized (HttpDownloader.this) { - running.add(this); - } - - download(); - - synchronized (HttpDownloader.this) { - downloaded += size; - } - } catch (IOException e) { - synchronized (HttpDownloader.this) { - failed.add(this); - } - } catch (InterruptedException e) { - log.info("Download of " + destFile + " was interrupted"); - } finally { - synchronized (HttpDownloader.this) { - left--; - running.remove(this); - } - } - } - - private void download() throws IOException, InterruptedException { - log.log(Level.INFO, "Downloading " + destFile + " from " + urls); - - File destDir = destFile.getParentFile(); - File tempFile = new File(destDir, destFile.getName() + ".tmp"); - destDir.mkdirs(); - - // Try to download - download(tempFile); - - destFile.delete(); - if (!tempFile.renameTo(destFile)) { - throw new IOException(String.format("Failed to rename %s to %s", tempFile, destFile)); - } - } - - private void download(File file) throws IOException, InterruptedException { - int trial = 0; - boolean first = true; - IOException lastException = null; - - do { - for (URL url : urls) { - // Sleep between each trial - if (!first) { - Thread.sleep((long) (retryDelay / 2 + (random.nextDouble() * retryDelay))); - } - first = false; - - try { - request = HttpRequest.get(url); - request.execute().expectResponseCode(200).saveContent(file); - return; - } catch (IOException e) { - lastException = e; - log.log(Level.WARNING, "Failed to download " + url, e); - } - } - } while (++trial < tryCount); - - throw new IOException("Failed to download from " + urls, lastException); - } - - @Override - public double getProgress() { - HttpRequest request = this.request; - return request != null ? request.getProgress() : -1; - } - - @Override - public String getStatus() { - double progress = getProgress(); - if (progress >= 0) { - return _("downloader.jobProgress", name, Math.round(progress * 100 * 100) / 100.0); - } else { - return _("downloader.jobPending", name); - } - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/install/InstallLog.java b/src/main/java/com/skcraft/launcher/install/InstallLog.java deleted file mode 100644 index 7398200..0000000 --- a/src/main/java/com/skcraft/launcher/install/InstallLog.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Data; -import lombok.NonNull; - -import java.io.File; -import java.net.URI; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import static com.google.common.base.Preconditions.checkNotNull; - -@Data -public class InstallLog { - - @JsonIgnore - private File baseDir; - private Map> entries = new HashMap>(); - @JsonIgnore - private Set cache = new HashSet(); - - public synchronized void add(@NonNull String group, @NonNull String entry) { - cache.add(entry); - Set subEntries = entries.get(group); - if (subEntries == null) { - subEntries = new HashSet(); - entries.put(group, subEntries); - } - subEntries.add(entry); - } - - public synchronized void add(@NonNull File group, @NonNull File entry) { - add(relativize(group), relativize(entry)); - } - - public synchronized boolean has(@NonNull String entry) { - return cache.contains(entry); - } - - public synchronized boolean has(@NonNull File entry) { - return has(relativize(entry)); - } - - public synchronized boolean copyGroupFrom(InstallLog other, String group) { - Set otherSet = other.entries.get(group); - if (otherSet == null) { - return false; - } - for (String entry : otherSet) { - add(group, entry); - } - return true; - } - - public synchronized boolean copyGroupFrom(@NonNull InstallLog other, @NonNull File entry) { - return copyGroupFrom(other, relativize(entry)); - } - - @JsonIgnore - public synchronized Set>> getEntrySet() { - return entries.entrySet(); - } - - public synchronized boolean hasGroup(String group) { - return entries.containsKey(group); - } - - private String relativize(File child) { - checkNotNull(baseDir); - URI uri = child.toURI(); - String relative = baseDir.toURI().relativize(uri).getPath(); - if (relative.equals(uri.toString())) { - throw new IllegalArgumentException("Child path not in base"); - } - return relative; - } - -} diff --git a/src/main/java/com/skcraft/launcher/install/InstallLogFileMover.java b/src/main/java/com/skcraft/launcher/install/InstallLogFileMover.java deleted file mode 100644 index a34c093..0000000 --- a/src/main/java/com/skcraft/launcher/install/InstallLogFileMover.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import lombok.NonNull; -import lombok.extern.java.Log; - -import java.io.File; -import java.io.IOException; -import java.util.logging.Level; - -import static com.skcraft.launcher.util.SharedLocale._; - -@Log -public class InstallLogFileMover implements InstallTask { - - private final InstallLog installLog; - private final File from; - private final File to; - - public InstallLogFileMover(InstallLog installLog, @NonNull File from, @NonNull File to) { - this.installLog = installLog; - this.from = from; - this.to = to; - } - - @Override - public void execute() throws IOException { - InstallLogFileMover.log.log(Level.INFO, "Installing to {0} (from {1})...", new Object[]{to.getAbsoluteFile(), from.getName()}); - to.getParentFile().mkdirs(); - to.delete(); - from.renameTo(to); - installLog.add(to, to); - } - - @Override - public double getProgress() { - return -1; - } - - @Override - public String getStatus() { - return _("installer.movingFile", from, to); - } - -} diff --git a/src/main/java/com/skcraft/launcher/install/InstallTask.java b/src/main/java/com/skcraft/launcher/install/InstallTask.java deleted file mode 100644 index 8988002..0000000 --- a/src/main/java/com/skcraft/launcher/install/InstallTask.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import com.skcraft.concurrency.ProgressObservable; - -public interface InstallTask extends ProgressObservable { - - void execute() throws Exception; - -} diff --git a/src/main/java/com/skcraft/launcher/install/Installer.java b/src/main/java/com/skcraft/launcher/install/Installer.java deleted file mode 100644 index 0ab03d2..0000000 --- a/src/main/java/com/skcraft/launcher/install/Installer.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import com.skcraft.concurrency.ProgressObservable; -import lombok.Getter; -import lombok.NonNull; -import lombok.extern.java.Log; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static com.skcraft.launcher.LauncherUtils.checkInterrupted; -import static com.skcraft.launcher.util.SharedLocale._; - -@Log -public class Installer implements ProgressObservable { - - @Getter private final File tempDir; - private final HttpDownloader downloader; - private InstallTask running; - private int count = 0; - private int finished = 0; - - private List queue = new ArrayList(); - - public Installer(@NonNull File tempDir) { - this.tempDir = tempDir; - this.downloader = new HttpDownloader(tempDir); - } - - public synchronized void queue(@NonNull InstallTask runnable) { - queue.add(runnable); - count++; - } - - public void download() throws IOException, InterruptedException { - downloader.execute(); - } - - public synchronized void execute() throws Exception { - queue = Collections.unmodifiableList(queue); - - try { - for (InstallTask runnable : queue) { - checkInterrupted(); - running = runnable; - runnable.execute(); - finished++; - } - } finally { - running = null; - } - } - - public Downloader getDownloader() { - return downloader; - } - - @Override - public double getProgress() { - return finished / (double) count; - } - - @Override - public String getStatus() { - InstallTask running = this.running; - if (running != null) { - String status = running.getStatus(); - if (status == null) { - status = running.toString(); - } - return _("installer.executing", count - finished) + "\n" + status; - } else { - return _("installer.installing"); - } - } -} diff --git a/src/main/java/com/skcraft/launcher/install/UpdateCache.java b/src/main/java/com/skcraft/launcher/install/UpdateCache.java deleted file mode 100644 index a33d9d8..0000000 --- a/src/main/java/com/skcraft/launcher/install/UpdateCache.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import lombok.Data; -import lombok.NonNull; - -import java.util.HashMap; -import java.util.Map; - -@Data -public class UpdateCache { - - private Map cache = new HashMap(); - - public synchronized boolean mark(@NonNull String key, @NonNull String version) { - String current = cache.get(key); - if (current != null && version.equals(current)) { - return false; - } else { - cache.put(key, version); - return true; - } - } -} diff --git a/src/main/java/com/skcraft/launcher/install/ZipExtract.java b/src/main/java/com/skcraft/launcher/install/ZipExtract.java deleted file mode 100644 index 9cc8c48..0000000 --- a/src/main/java/com/skcraft/launcher/install/ZipExtract.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.install; - -import com.google.common.io.ByteSource; -import com.google.common.io.Closer; -import lombok.Getter; -import lombok.NonNull; -import lombok.Setter; -import org.apache.commons.io.IOUtils; - -import java.io.*; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import static org.apache.commons.io.IOUtils.closeQuietly; - -public class ZipExtract implements Runnable { - - @Getter private final ByteSource source; - @Getter private final File destination; - @Getter @Setter - private List exclude; - - public ZipExtract(@NonNull ByteSource source, @NonNull File destination) { - this.source = source; - this.destination = destination; - } - - @Override - public void run() { - Closer closer = Closer.create(); - - try { - InputStream is = closer.register(source.openBufferedStream()); - ZipInputStream zis = closer.register(new ZipInputStream(is)); - ZipEntry entry; - - destination.getParentFile().mkdirs(); - - while ((entry = zis.getNextEntry()) != null) { - if (matches(entry)) { - File file = new File(getDestination(), entry.getName()); - writeEntry(zis, file); - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - try { - closer.close(); - } catch (IOException e) { - } - } - } - - /** - * Checks if the given entry should be extracted. - * - * @param entry the entry - * @return true if the entry matches the filter - */ - private boolean matches(ZipEntry entry) { - if (exclude != null) { - for (String pattern : exclude) { - if (entry.getName().startsWith(pattern)) { - return false; - } - } - } - - return true; - } - - private void writeEntry(ZipInputStream zis, File path) throws IOException { - FileOutputStream fos = null; - BufferedOutputStream bos = null; - - try { - path.getParentFile().mkdirs(); - - fos = new FileOutputStream(path); - bos = new BufferedOutputStream(fos); - IOUtils.copy(zis, bos); - } finally { - closeQuietly(bos); - closeQuietly(fos); - } - } - - @Override - public String toString() { - return destination.getName(); - } - - -} diff --git a/src/main/java/com/skcraft/launcher/launch/JavaProcessBuilder.java b/src/main/java/com/skcraft/launcher/launch/JavaProcessBuilder.java deleted file mode 100644 index 726cdbf..0000000 --- a/src/main/java/com/skcraft/launcher/launch/JavaProcessBuilder.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.launch; - -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A tool to build the entire command line used to launch a Java process. - * It combines flags, memory settings, arguments, the class path, and - * the main class. - */ -@ToString -public class JavaProcessBuilder { - - private static final Pattern argsPattern = Pattern.compile("(?:([^\"]\\S*)|\"(.+?)\")\\s*"); - - @Getter @Setter private File jvmPath = JavaRuntimeFinder.findBestJavaPath(); - @Getter @Setter private int minMemory; - @Getter @Setter private int maxMemory; - @Getter @Setter private int permGen; - - @Getter private final List classPath = new ArrayList(); - @Getter private final List flags = new ArrayList(); - @Getter private final List args = new ArrayList(); - @Getter @Setter private String mainClass; - - public void tryJvmPath(File path) throws IOException { - // Try the parent directory - if (!path.exists()) { - throw new IOException( - "The configured Java runtime path '" + path + "' doesn't exist."); - } else if (path.isFile()) { - path = path.getParentFile(); - } - - File binDir = new File(path, "bin"); - if (binDir.isDirectory()) { - path = binDir; - } - - setJvmPath(path); - } - - public JavaProcessBuilder classPath(File file) { - getClassPath().add(file); - return this; - } - - public JavaProcessBuilder classPath(String path) { - getClassPath().add(new File(path)); - return this; - } - - public String buildClassPath() { - StringBuilder builder = new StringBuilder(); - boolean first = true; - - for (File file : classPath) { - if (first) { - first = false; - } else { - builder.append(File.pathSeparator); - } - - builder.append(file.getAbsolutePath()); - } - - return builder.toString(); - } - - public List buildCommand() { - List command = new ArrayList(); - - if (getJvmPath() != null) { - command.add(getJvmPath() + File.separator + "java"); - } else { - command.add("java"); - } - - for (String flag : flags) { - command.add(flag); - } - - if (minMemory > 0) { - command.add("-Xms" + String.valueOf(minMemory) + "M"); - } - - if (maxMemory > 0) { - command.add("-Xmx" + String.valueOf(maxMemory) + "M"); - } - - if (permGen > 0) { - command.add("-XX:MaxPermSize=" + String.valueOf(permGen) + "M"); - } - - command.add("-cp"); - command.add(buildClassPath()); - - command.add(mainClass); - - for (String arg : args) { - command.add(arg); - } - - return command; - } - - /** - * Split the given string as simple command line arguments. - * - *

This is not to be used for security purposes.

- * - * @param str the string - * @return the split args - */ - public static List splitArgs(String str) { - Matcher matcher = argsPattern.matcher(str); - List parts = new ArrayList(); - while (matcher.find()) { - parts.add(matcher.group(1)); - } - return parts; - } - -} diff --git a/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java b/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java deleted file mode 100644 index 6926176..0000000 --- a/src/main/java/com/skcraft/launcher/launch/JavaRuntimeFinder.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.launch; - -import com.skcraft.launcher.util.Environment; -import com.skcraft.launcher.util.Platform; -import com.skcraft.launcher.util.WinRegistry; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Finds the best Java runtime to use. - */ -public final class JavaRuntimeFinder { - - private JavaRuntimeFinder() { - } - - /** - * Return the path to the best found JVM location. - * - * @return the JVM location, or null - */ - public static File findBestJavaPath() { - if (Environment.getInstance().getPlatform() != Platform.WINDOWS) { - return null; - } - - List entries = new ArrayList(); - try { - getEntriesFromRegistry(entries, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); - getEntriesFromRegistry(entries, "SOFTWARE\\JavaSoft\\Java Development Kit"); - } catch (Throwable e) { - } - Collections.sort(entries); - - if (entries.size() > 0) { - return new File(entries.get(0).dir, "bin"); - } - - return null; - } - - private static void getEntriesFromRegistry(List entries, String basePath) - throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { - List subKeys = WinRegistry.readStringSubKeys( - WinRegistry.HKEY_LOCAL_MACHINE, basePath); - for (String subKey : subKeys) { - JREEntry entry = getEntryFromRegistry(basePath, subKey); - if (entry != null) { - entries.add(entry); - } - } - } - - private static JREEntry getEntryFromRegistry(String basePath, String version) - throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { - String regPath = basePath + "\\" + version; - String path = WinRegistry.readString( - WinRegistry.HKEY_LOCAL_MACHINE, regPath, "JavaHome"); - File dir = new File(path); - if (dir.exists() && new File(dir, "bin/java.exe").exists()) { - JREEntry entry = new JREEntry(); - entry.dir = dir; - entry.version = version; - entry.is64Bit = guessIf64Bit(dir); - return entry; - } else { - return null; - } - } - - private static boolean guessIf64Bit(File path) { - String programFilesX86 = System.getenv("ProgramFiles(x86)"); - if (programFilesX86 == null) { - return true; - } - return !path.toString().startsWith(new File(programFilesX86).toString()); - } - - private static class JREEntry implements Comparable { - private File dir; - private String version; - private boolean is64Bit; - - @Override - public int compareTo(JREEntry o) { - if (is64Bit && !o.is64Bit) { - return -1; - } else if (!is64Bit && o.is64Bit) { - return 1; - } - - String[] a = version.split("[\\._]"); - String[] b = o.version.split("[\\._]"); - int min = Math.min(a.length, b.length); - - for (int i = 0; i < min; i++) { - int first, second; - - try { - first = Integer.parseInt(a[i]); - } catch (NumberFormatException e) { - return -1; - } - - try { - second = Integer.parseInt(b[i]); - } catch (NumberFormatException e) { - return 1; - } - - if (first > second) { - return -1; - } else if (first < second) { - return 1; - } - } - - if (a.length == b.length) { - return 0; // Same - } - - return a.length > b.length ? -1 : 1; - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/launch/LaunchProcessHandler.java b/src/main/java/com/skcraft/launcher/launch/LaunchProcessHandler.java deleted file mode 100644 index 0e4934e..0000000 --- a/src/main/java/com/skcraft/launcher/launch/LaunchProcessHandler.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.launch; - -import com.google.common.base.Function; -import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.dialog.LauncherFrame; -import com.skcraft.launcher.dialog.ProcessConsoleFrame; -import com.skcraft.launcher.swing.MessageLog; -import lombok.NonNull; -import lombok.extern.java.Log; - -import javax.swing.*; -import java.lang.reflect.InvocationTargetException; -import java.util.logging.Level; - -/** - * Handles post-process creation during launch. - */ -@Log -public class LaunchProcessHandler implements Function { - - private static final int CONSOLE_NUM_LINES = 10000; - - private final Launcher launcher; - private ProcessConsoleFrame consoleFrame; - - public LaunchProcessHandler(@NonNull Launcher launcher) { - this.launcher = launcher; - } - - @Override - public ProcessConsoleFrame apply(final Process process) { - log.info("Watching process " + process); - - try { - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - consoleFrame = new ProcessConsoleFrame(CONSOLE_NUM_LINES, true); - consoleFrame.setProcess(process); - consoleFrame.setVisible(true); - MessageLog messageLog = consoleFrame.getMessageLog(); - messageLog.consume(process.getInputStream()); - messageLog.consume(process.getErrorStream()); - } - }); - - // Wait for the process to end - process.waitFor(); - } catch (InterruptedException e) { - // Orphan process - } catch (InvocationTargetException e) { - log.log(Level.WARNING, "Unexpected failure", e); - } - - log.info("Process ended, re-showing launcher..."); - - // Restore the launcher - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - new LauncherFrame(launcher).setVisible(true); - - if (consoleFrame != null) { - consoleFrame.setProcess(null); - consoleFrame.requestFocus(); - } - } - }); - - return consoleFrame; - } - -} diff --git a/src/main/java/com/skcraft/launcher/launch/Runner.java b/src/main/java/com/skcraft/launcher/launch/Runner.java deleted file mode 100644 index 7f5d426..0000000 --- a/src/main/java/com/skcraft/launcher/launch/Runner.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.launch; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Strings; -import com.google.common.io.Files; -import com.skcraft.concurrency.DefaultProgress; -import com.skcraft.concurrency.ProgressObservable; -import com.skcraft.launcher.*; -import com.skcraft.launcher.auth.Session; -import com.skcraft.launcher.install.ZipExtract; -import com.skcraft.launcher.model.minecraft.AssetsIndex; -import com.skcraft.launcher.model.minecraft.Library; -import com.skcraft.launcher.model.minecraft.VersionManifest; -import com.skcraft.launcher.persistence.Persistence; -import com.skcraft.launcher.util.Environment; -import com.skcraft.launcher.util.Platform; -import lombok.Getter; -import lombok.NonNull; -import lombok.Setter; -import lombok.extern.java.Log; -import org.apache.commons.lang.text.StrSubstitutor; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; - -import static com.skcraft.launcher.LauncherUtils.checkInterrupted; -import static com.skcraft.launcher.util.SharedLocale._; - -/** - * Handles the launching of an instance. - */ -@Log -public class Runner implements Callable, ProgressObservable { - - private ProgressObservable progress = new DefaultProgress(0, _("runner.preparing")); - - private final ObjectMapper mapper = new ObjectMapper(); - private final Launcher launcher; - private final Instance instance; - private final Session session; - private final File extractDir; - @Getter @Setter private Environment environment = Environment.getInstance(); - - private VersionManifest versionManifest; - private AssetsIndex assetsIndex; - private File virtualAssetsDir; - private Configuration config; - private JavaProcessBuilder builder; - private AssetsRoot assetsRoot; - - /** - * Create a new instance launcher. - * - * @param launcher the launcher - * @param instance the instance - * @param session the session - * @param extractDir the directory to extract to - */ - public Runner(@NonNull Launcher launcher, @NonNull Instance instance, - @NonNull Session session, @NonNull File extractDir) { - this.launcher = launcher; - this.instance = instance; - this.session = session; - this.extractDir = extractDir; - } - - /** - * Get the path to the JAR. - * - * @return the JAR path - */ - private File getJarPath() { - File jarPath = instance.getCustomJarPath(); - if (!jarPath.exists()) { - jarPath = launcher.getJarPath(versionManifest); - } - return jarPath; - } - - @Override - public Process call() throws Exception { - if (!instance.isInstalled()) { - throw new LauncherException("Update required", _("runner.updateRequired")); - } - - config = launcher.getConfig(); - builder = new JavaProcessBuilder(); - assetsRoot = launcher.getAssets(); - - // Load manifiests - versionManifest = mapper.readValue(instance.getVersionPath(), VersionManifest.class); - - // Load assets index - File assetsFile = assetsRoot.getIndexPath(versionManifest); - try { - assetsIndex = mapper.readValue(assetsFile, AssetsIndex.class); - } catch (FileNotFoundException e) { - instance.setInstalled(false); - Persistence.commitAndForget(instance); - throw new LauncherException("Missing assets index " + assetsFile.getAbsolutePath(), - _("runner.missingAssetsIndex", instance.getTitle(), assetsFile.getAbsolutePath())); - } catch (IOException e) { - instance.setInstalled(false); - Persistence.commitAndForget(instance); - throw new LauncherException("Corrupt assets index " + assetsFile.getAbsolutePath(), - _("runner.corruptAssetsIndex", instance.getTitle(), assetsFile.getAbsolutePath())); - } - - // Copy over assets to the tree - try { - AssetsRoot.AssetsTreeBuilder assetsBuilder = assetsRoot.createAssetsBuilder(versionManifest); - progress = assetsBuilder; - virtualAssetsDir = assetsBuilder.build(); - } catch (LauncherException e) { - instance.setInstalled(false); - Persistence.commitAndForget(instance); - throw e; - } - - progress = new DefaultProgress(0.9, _("runner.collectingArgs")); - - addJvmArgs(); - addLibraries(); - addJarArgs(); - addProxyArgs(); - addWindowArgs(); - addPlatformArgs(); - - builder.classPath(getJarPath()); - builder.setMainClass(versionManifest.getMainClass()); - - callLaunchModifier(); - - ProcessBuilder processBuilder = new ProcessBuilder(builder.buildCommand()); - processBuilder.directory(instance.getContentDir()); - Runner.log.info("Launching: " + builder); - checkInterrupted(); - - progress = new DefaultProgress(1, _("runner.startingJava")); - - return processBuilder.start(); - } - - /** - * Call the manifest launch modifier. - */ - private void callLaunchModifier() { - instance.modify(builder); - } - - /** - * Add platform-specific arguments. - */ - private void addPlatformArgs() { - // Mac OS X arguments - if (getEnvironment().getPlatform() == Platform.MAC_OS_X) { - File icnsPath = assetsIndex.getObjectPath(assetsRoot, "icons/minecraft.icns"); - if (icnsPath != null) { - builder.getFlags().add("-Xdock:icon=" + icnsPath.getAbsolutePath()); - builder.getFlags().add("-Xdock:name=Minecraft"); - } - } - - // Windows arguments - if (getEnvironment().getPlatform() == Platform.WINDOWS) { - builder.getFlags().add("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"); - } - } - - /** - * Add libraries. - */ - private void addLibraries() throws LauncherException { - // Add libraries to classpath or extract the libraries as necessary - for (Library library : versionManifest.getLibraries()) { - if (!library.matches(environment)) { - continue; - } - - File path = new File(launcher.getLibrariesDir(), library.getPath(environment)); - - if (path.exists()) { - Library.Extract extract = library.getExtract(); - if (extract != null) { - ZipExtract zipExtract = new ZipExtract(Files.asByteSource(path), extractDir); - zipExtract.setExclude(extract.getExclude()); - zipExtract.run(); - } else { - builder.classPath(path); - } - } else { - instance.setInstalled(false); - Persistence.commitAndForget(instance); - throw new LauncherException("Missing library " + library.getName(), - _("runner.missingLibrary", instance.getTitle(), library.getName())); - } - } - - builder.getFlags().add("-Djava.library.path=" + extractDir.getAbsoluteFile()); - } - - /** - * Add JVM arguments. - * - * @throws IOException on I/O error - */ - private void addJvmArgs() throws IOException { - int minMemory = config.getMinMemory(); - int maxMemory = config.getMaxMemory(); - int permGen = config.getPermGen(); - - if (minMemory <= 0) { - minMemory = 1024; - } - - if (maxMemory <= 0) { - maxMemory = 1024; - } - - if (permGen <= 0) { - permGen = 128; - } - - if (permGen <= 64) { - permGen = 64; - } - - if (minMemory > maxMemory) { - maxMemory = minMemory; - } - - builder.setMinMemory(minMemory); - builder.setMaxMemory(maxMemory); - builder.setPermGen(permGen); - - String rawJvmPath = config.getJvmPath(); - if (!Strings.isNullOrEmpty(rawJvmPath)) { - builder.tryJvmPath(new File(rawJvmPath)); - } - - String rawJvmArgs = config.getJvmArgs(); - if (!Strings.isNullOrEmpty(rawJvmArgs)) { - List flags = builder.getFlags(); - - for (String arg : JavaProcessBuilder.splitArgs(rawJvmArgs)) { - flags.add(arg); - } - } - } - - /** - * Add arguments for the application. - * - * @throws JsonProcessingException on error - */ - private void addJarArgs() throws JsonProcessingException { - List args = builder.getArgs(); - - String[] rawArgs = versionManifest.getMinecraftArguments().split(" +"); - StrSubstitutor substitutor = new StrSubstitutor(getCommandSubstitutions()); - for (String arg : rawArgs) { - args.add(substitutor.replace(arg)); - } - } - - /** - * Add proxy arguments. - */ - private void addProxyArgs() { - List args = builder.getArgs(); - - if (config.isProxyEnabled()) { - String host = config.getProxyHost(); - int port = config.getProxyPort(); - String username = config.getProxyUsername(); - String password = config.getProxyPassword(); - - if (!Strings.isNullOrEmpty(host) && port > 0 && port < 65535) { - args.add("--proxyHost"); - args.add(config.getProxyHost()); - args.add("--proxyPort"); - args.add(String.valueOf(port)); - - if (!Strings.isNullOrEmpty(username)) { - builder.getArgs().add("--proxyUser"); - builder.getArgs().add(username); - builder.getArgs().add("--proxyPass"); - builder.getArgs().add(password); - } - } - } - } - - /** - * Add window arguments. - */ - private void addWindowArgs() { - List args = builder.getArgs(); - int width = config.getWindowWidth(); - int height = config.getWidowHeight(); - - if (width >= 10) { - args.add("--width"); - args.add(String.valueOf(width)); - args.add("--height"); - args.add(String.valueOf(height)); - } - } - - /** - * Build the list of command substitutions. - * - * @return the map of substitutions - * @throws JsonProcessingException on error - */ - private Map getCommandSubstitutions() throws JsonProcessingException { - Map map = new HashMap(); - - map.put("version_name", versionManifest.getId()); - - map.put("auth_access_token", session.getAccessToken()); - map.put("auth_session", session.getSessionToken()); - map.put("auth_player_name", session.getName()); - map.put("auth_uuid", session.getUuid()); - - map.put("profile_name", session.getName()); - map.put("user_type", session.getUserType().getName()); - map.put("user_properties", mapper.writeValueAsString(session.getUserProperties())); - - map.put("game_directory", instance.getContentDir().getAbsolutePath()); - map.put("game_assets", virtualAssetsDir.getAbsolutePath()); - map.put("assets_root", launcher.getAssets().getDir().getAbsolutePath()); - map.put("assets_index_name", versionManifest.getAssetsIndex()); - - return map; - } - - @Override - public double getProgress() { - return progress.getProgress(); - } - - @Override - public String getStatus() { - return progress.getStatus(); - } - -} diff --git a/src/main/java/com/skcraft/launcher/model/loader/InstallData.java b/src/main/java/com/skcraft/launcher/model/loader/InstallData.java deleted file mode 100644 index c346dc6..0000000 --- a/src/main/java/com/skcraft/launcher/model/loader/InstallData.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.loader; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; - -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class InstallData { - - private String path; - private String filePath; - -} diff --git a/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java b/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java deleted file mode 100644 index 6205501..0000000 --- a/src/main/java/com/skcraft/launcher/model/loader/InstallProfile.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.loader; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class InstallProfile { - - @JsonProperty("install") - private InstallData installData; - private VersionInfo versionInfo; - -} diff --git a/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java b/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java deleted file mode 100644 index f9872c4..0000000 --- a/src/main/java/com/skcraft/launcher/model/loader/VersionInfo.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.loader; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.skcraft.launcher.model.minecraft.Library; -import lombok.Data; - -import java.util.List; - -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class VersionInfo { - - private String minecraftArguments; - private String mainClass; - private List libraries; - -} diff --git a/src/main/java/com/skcraft/launcher/model/minecraft/Asset.java b/src/main/java/com/skcraft/launcher/model/minecraft/Asset.java deleted file mode 100644 index 122e9ce..0000000 --- a/src/main/java/com/skcraft/launcher/model/minecraft/Asset.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.minecraft; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; - -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class Asset { - - private String hash; - private int size; - -} diff --git a/src/main/java/com/skcraft/launcher/model/minecraft/AssetsIndex.java b/src/main/java/com/skcraft/launcher/model/minecraft/AssetsIndex.java deleted file mode 100644 index 53f56a0..0000000 --- a/src/main/java/com/skcraft/launcher/model/minecraft/AssetsIndex.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.minecraft; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.skcraft.launcher.AssetsRoot; -import lombok.Data; -import lombok.NonNull; - -import java.io.File; -import java.util.Map; - -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class AssetsIndex { - - private boolean virtual; - private Map objects; - - public File getObjectPath(@NonNull AssetsRoot assetsRoot, @NonNull String name) { - Asset asset = objects.get(name); - if (asset != null) { - return assetsRoot.getObjectPath(asset); - } else { - return null; - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/model/minecraft/Library.java b/src/main/java/com/skcraft/launcher/model/minecraft/Library.java deleted file mode 100644 index ccb13de..0000000 --- a/src/main/java/com/skcraft/launcher/model/minecraft/Library.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.minecraft; - -import com.fasterxml.jackson.annotation.*; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.skcraft.launcher.util.Environment; -import com.skcraft.launcher.util.Platform; -import lombok.Data; - -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class Library { - - private String name; - private transient String group; - private transient String artifact; - private transient String version; - @JsonProperty("url") - private String baseUrl; - private Map natives; - private Extract extract; - private List rules; - - // Forge-added - private String comment; - - // Custom - private boolean locallyAvailable; - - public void setName(String name) { - this.name = name; - - if (name != null) { - String[] parts = name.split(":"); - this.group = parts[0]; - this.artifact = parts[1]; - this.version = parts[2]; - } else { - this.group = null; - this.artifact = null; - this.version = null; - } - } - - public boolean matches(Environment environment) { - boolean allow = false; - - if (getRules() != null) { - for (Rule rule : getRules()) { - if (rule.matches(environment)) { - allow = rule.getAction() == Action.ALLOW; - } - } - } else { - allow = true; - } - - return allow; - } - - @JsonIgnore - public String getGroup() { - return group; - } - - @JsonIgnore - public String getArtifact() { - return artifact; - } - - @JsonIgnore - public String getVersion() { - return version; - } - - public String getNativeString(Platform platform) { - if (getNatives() != null) { - switch (platform) { - case LINUX: - return getNatives().get("linux"); - case WINDOWS: - return getNatives().get("windows"); - case MAC_OS_X: - return getNatives().get("osx"); - default: - return null; - } - } else { - return null; - } - } - - public String getFilename(Environment environment) { - String nativeString = getNativeString(environment.getPlatform()); - if (nativeString != null) { - return String.format("%s-%s-%s.jar", - getArtifact(), getVersion(), nativeString); - } - - return String.format("%s-%s.jar", getArtifact(), getVersion()); - } - - public String getPath(Environment environment) { - StringBuilder builder = new StringBuilder(); - builder.append(getGroup().replace('.', '/')); - builder.append("/"); - builder.append(getArtifact()); - builder.append("/"); - builder.append(getVersion()); - builder.append("/"); - builder.append(getFilename(environment)); - String path = builder.toString(); - path = path.replace("${arch}", environment.getArchBits()); - return path; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Library library = (Library) o; - - if (name != null ? !name.equals(library.name) : library.name != null) - return false; - - return true; - } - - @Override - public int hashCode() { - return name != null ? name.hashCode() : 0; - } - - @Data - public static class Rule { - private Action action; - private OS os; - - public boolean matches(Environment environment) { - if (getOs() == null) { - return true; - } else { - return getOs().matches(environment); - } - } - } - - @Data - public static class OS { - private Platform platform; - private Pattern version; - - @JsonProperty("name") - @JsonDeserialize(using = PlatformDeserializer.class) - @JsonSerialize(using = PlatformSerializer.class) - public Platform getPlatform() { - return platform; - } - - public boolean matches(Environment environment) { - return (getPlatform() == null || getPlatform().equals(environment.getPlatform())) && - (getVersion() == null || getVersion().matcher(environment.getPlatformVersion()).matches()); - } - } - - @Data - public static class Extract { - private List exclude; - } - - private enum Action { - ALLOW, - DISALLOW; - - @JsonCreator - public static Action fromJson(String text) { - return valueOf(text.toUpperCase()); - } - - @JsonValue - public String toJson() { - return name().toLowerCase(); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/model/minecraft/PlatformDeserializer.java b/src/main/java/com/skcraft/launcher/model/minecraft/PlatformDeserializer.java deleted file mode 100644 index 5eb8a51..0000000 --- a/src/main/java/com/skcraft/launcher/model/minecraft/PlatformDeserializer.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.minecraft; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.skcraft.launcher.util.Platform; - -import java.io.IOException; - -public class PlatformDeserializer extends JsonDeserializer { - - @Override - public Platform deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException { - String text = jsonParser.getText(); - if (text.equalsIgnoreCase("windows")) { - return Platform.WINDOWS; - } else if (text.equalsIgnoreCase("linux")) { - return Platform.LINUX; - } else if (text.equalsIgnoreCase("solaris")) { - return Platform.SOLARIS; - } else if (text.equalsIgnoreCase("osx")) { - return Platform.MAC_OS_X; - } else { - throw new IOException("Unknown platform: " + text); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/model/minecraft/PlatformSerializer.java b/src/main/java/com/skcraft/launcher/model/minecraft/PlatformSerializer.java deleted file mode 100644 index 91a44b1..0000000 --- a/src/main/java/com/skcraft/launcher/model/minecraft/PlatformSerializer.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.minecraft; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.skcraft.launcher.util.Platform; - -import java.io.IOException; - -public class PlatformSerializer extends JsonSerializer { - - @Override - public void serialize(Platform platform, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) - throws IOException, JsonProcessingException { - switch (platform) { - case WINDOWS: - jsonGenerator.writeString("windows"); - break; - case MAC_OS_X: - jsonGenerator.writeString("osx"); - break; - case LINUX: - jsonGenerator.writeString("linux"); - break; - case SOLARIS: - jsonGenerator.writeString("solaris"); - break; - case UNKNOWN: - jsonGenerator.writeNull(); - break; - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/model/minecraft/ReleaseList.java b/src/main/java/com/skcraft/launcher/model/minecraft/ReleaseList.java deleted file mode 100644 index 4cfcc1d..0000000 --- a/src/main/java/com/skcraft/launcher/model/minecraft/ReleaseList.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.minecraft; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; -import lombok.NonNull; - -import java.util.List; - -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class ReleaseList { - - private LatestReleases latest; - private List versions; - - /** - * Get a release with the given ID. - * - * @param id the ID - * @return the release - */ - public Version find(@NonNull String id) { - for (Version version : getVersions()) { - if (version.getId().equals(id)) { - return version; - } - } - return null; - } - - @Data - public static class LatestReleases { - private String snapshot; - private String release; - } - -} diff --git a/src/main/java/com/skcraft/launcher/model/minecraft/Version.java b/src/main/java/com/skcraft/launcher/model/minecraft/Version.java deleted file mode 100644 index c01f6cb..0000000 --- a/src/main/java/com/skcraft/launcher/model/minecraft/Version.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.minecraft; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Getter; -import lombok.NonNull; -import lombok.Setter; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class Version { - - @Getter - @Setter - @NonNull - private String id; - - public Version() { - } - - public Version(@NonNull String id) { - this.id = id; - } - - @JsonIgnore - public String getName() { - return id; - } - - @Override - public String toString() { - return getName(); - } - - boolean thisEquals(Version other) { - return getId().equals(other.getId()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Version version = (Version) o; - return thisEquals(version) && version.thisEquals(this); - } - - @Override - public int hashCode() { - return id.hashCode(); - } -} diff --git a/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java b/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java deleted file mode 100644 index 283bb8a..0000000 --- a/src/main/java/com/skcraft/launcher/model/minecraft/VersionManifest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.minecraft; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import lombok.Data; - -import java.util.Date; -import java.util.LinkedHashSet; - -@Data -@JsonIgnoreProperties(ignoreUnknown = true) -public class VersionManifest { - - private String id; - private Date time; - private Date releaseTime; - private String assets; - private String type; - private String processArguments; - private String minecraftArguments; - private String mainClass; - private int minimumLauncherVersion; - private LinkedHashSet libraries; - - @JsonIgnore - public String getAssetsIndex() { - return getAssets() != null ? getAssets() : "legacy"; - } - -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/BaseManifest.java b/src/main/java/com/skcraft/launcher/model/modpack/BaseManifest.java deleted file mode 100644 index 3aa84ea..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/BaseManifest.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import lombok.Data; - -@Data -public class BaseManifest { - - private String title; - private String name; - private String version; - -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/Condition.java b/src/main/java/com/skcraft/launcher/model/modpack/Condition.java deleted file mode 100644 index 8f0332c..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/Condition.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; - -@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="if") -@JsonSubTypes({ - @JsonSubTypes.Type(value = RequireAny.class, name = "requireAny"), - @JsonSubTypes.Type(value = RequireAll.class, name = "requireAll") -}) -public interface Condition { - - boolean matches(); - -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/Feature.java b/src/main/java/com/skcraft/launcher/model/modpack/Feature.java deleted file mode 100644 index 820e701..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/Feature.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIdentityInfo; -import com.fasterxml.jackson.annotation.JsonValue; -import com.fasterxml.jackson.annotation.ObjectIdGenerators; -import com.google.common.base.Strings; -import lombok.Data; - -@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="name") -@Data -public class Feature implements Comparable { - - public enum Recommendation { - STARRED, - AVOID; - - @JsonCreator - public static Recommendation fromJson(String text) { - return valueOf(text.toUpperCase()); - } - - @JsonValue - public String toJson() { - return name().toLowerCase(); - }; - }; - - private String name; - private String description; - private Recommendation recommendation; - private boolean selected; - - public Feature() { - } - - public Feature(String name, String description, boolean selected) { - this.name = name; - this.description = description; - this.selected = selected; - } - - public Feature(Feature feature) { - setName(feature.getName()); - setDescription(feature.getDescription()); - setSelected(feature.isSelected()); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object other) { - return super.equals(other); - } - - @Override - public int compareTo(Feature o) { - return Strings.nullToEmpty(getName()).compareTo(Strings.nullToEmpty(o.getName())); - } -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/FileInstall.java b/src/main/java/com/skcraft/launcher/model/modpack/FileInstall.java deleted file mode 100644 index 3964787..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/FileInstall.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; -import com.google.common.io.Files; -import com.skcraft.launcher.install.InstallLog; -import com.skcraft.launcher.install.InstallLogFileMover; -import com.skcraft.launcher.install.Installer; -import com.skcraft.launcher.install.UpdateCache; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NonNull; -import org.apache.commons.io.FilenameUtils; - -import java.io.File; -import java.io.IOException; -import java.net.URL; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.skcraft.launcher.LauncherUtils.concat; - -@Data -@EqualsAndHashCode(callSuper = false) -public class FileInstall extends ManifestEntry { - - private static HashFunction hf = Hashing.sha1(); - private String version; - private String hash; - private String location; - private String to; - private long size; - private boolean userFile; - - @JsonIgnore - public String getImpliedVersion() { - return checkNotNull(version != null ? version : hash); - } - - @JsonIgnore - public String getTargetPath() { - return checkNotNull(this.to != null ? this.to : location); - } - - @Override - public void install(@NonNull Installer installer, @NonNull InstallLog log, - @NonNull UpdateCache cache, @NonNull File contentDir) throws IOException { - if (getWhen() != null && !getWhen().matches()) { - return; - } - - String targetPath = getTargetPath(); - File targetFile = new File(contentDir, targetPath); - String fileVersion = getImpliedVersion(); - URL url = concat(getManifest().getObjectsUrl(), getLocation()); - - if (shouldUpdate(cache, targetFile)) { - long size = this.size; - if (size <= 0) { - size = 10 * 1024; - } - - File tempFile = installer.getDownloader().download(url, fileVersion, size, to); - installer.queue(new InstallLogFileMover(log, tempFile, targetFile)); - } else { - log.add(to, to); - } - } - - private boolean shouldUpdate(UpdateCache cache, File targetFile) throws IOException { - if (targetFile.exists() && isUserFile()) { - return false; - } - - if (!targetFile.exists()) { - return true; - } - - if (hash != null) { - String existingHash = Files.hash(targetFile, hf).toString(); - if (existingHash.equalsIgnoreCase(hash)) { - return false; - } - } - - return cache.mark(FilenameUtils.normalize(getTargetPath()), getImpliedVersion()); - } - -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/LaunchModifier.java b/src/main/java/com/skcraft/launcher/model/modpack/LaunchModifier.java deleted file mode 100644 index e8b961a..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/LaunchModifier.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import com.skcraft.launcher.launch.JavaProcessBuilder; -import lombok.Data; - -import java.util.List; - -@Data -public class LaunchModifier { - - private List flags; - - public void modify(JavaProcessBuilder builder) { - if (flags != null) { - for (String flag : flags) { - builder.getFlags().add(flag); - } - } - } -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/Manifest.java b/src/main/java/com/skcraft/launcher/model/modpack/Manifest.java deleted file mode 100644 index cac0f7f..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/Manifest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonManagedReference; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.Strings; -import com.skcraft.launcher.Instance; -import com.skcraft.launcher.LauncherUtils; -import com.skcraft.launcher.model.minecraft.VersionManifest; -import com.skcraft.launcher.install.Installer; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; - -@Data -@EqualsAndHashCode(callSuper = true) -public class Manifest extends BaseManifest { - - public static final int MIN_PROTOCOL_VERSION = 2; - - private int minimumVersion; - private URL baseUrl; - private String librariesLocation; - private String objectsLocation; - private String gameVersion; - @JsonProperty("launch") - private LaunchModifier launchModifier; - private List features = new ArrayList(); - @JsonManagedReference("manifest") - private List tasks = new ArrayList(); - @Getter @Setter @JsonIgnore - private Installer installer; - private VersionManifest versionManifest; - - @JsonIgnore - public URL getLibrariesUrl() { - if (Strings.nullToEmpty(getLibrariesLocation()) == null) { - return null; - } - - try { - return LauncherUtils.concat(baseUrl, Strings.nullToEmpty(getLibrariesLocation()) + "/"); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - @JsonIgnore - public URL getObjectsUrl() { - if (Strings.nullToEmpty(getObjectsLocation()) == null) { - return baseUrl; - } - - try { - return LauncherUtils.concat(baseUrl, Strings.nullToEmpty(getObjectsLocation()) + "/"); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - public void updateName(String name) { - if (name != null) { - setName(name); - } - } - - public void updateTitle(String title) { - if (title != null) { - setTitle(title); - } - } - - public void updateGameVersion(String gameVersion) { - if (gameVersion != null) { - setGameVersion(gameVersion); - } - } - - public void update(Instance instance) { - instance.setLaunchModifier(getLaunchModifier()); - } -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/ManifestEntry.java b/src/main/java/com/skcraft/launcher/model/modpack/ManifestEntry.java deleted file mode 100644 index 8d06fad..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/ManifestEntry.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import com.fasterxml.jackson.annotation.JsonBackReference; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.skcraft.launcher.install.InstallLog; -import com.skcraft.launcher.install.Installer; -import com.skcraft.launcher.install.UpdateCache; -import lombok.Data; -import lombok.ToString; - -import java.io.File; - -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.PROPERTY, - property = "type", - defaultImpl = FileInstall.class) -@JsonSubTypes({ - @JsonSubTypes.Type(value = FileInstall.class, name = "file") -}) -@Data -@ToString(exclude = "manifest") -public abstract class ManifestEntry { - - @JsonBackReference("manifest") - private Manifest manifest; - private Condition when; - - public abstract void install(Installer installer, InstallLog log, UpdateCache cache, File contentDir) throws Exception; - -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/ManifestInfo.java b/src/main/java/com/skcraft/launcher/model/modpack/ManifestInfo.java deleted file mode 100644 index e035579..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/ManifestInfo.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -@Data -@EqualsAndHashCode(callSuper = true) -public class ManifestInfo extends BaseManifest { - - private String location; - private int priority; - -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/PackageList.java b/src/main/java/com/skcraft/launcher/model/modpack/PackageList.java deleted file mode 100644 index 8385ff3..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/PackageList.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import lombok.Data; - -import java.util.List; - -@Data -public class PackageList { - - private int minimumVersion; - private List packages; - -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/RequireAll.java b/src/main/java/com/skcraft/launcher/model/modpack/RequireAll.java deleted file mode 100644 index ce84174..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/RequireAll.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import lombok.Data; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@Data -public class RequireAll implements Condition { - - private List features = new ArrayList(); - - public RequireAll() { - } - - public RequireAll(List features) { - this.features = features; - } - - public RequireAll(Feature... feature) { - features.addAll(Arrays.asList(feature)); - } - - @Override - public boolean matches() { - if (features == null) { - return true; - } - - for (Feature feature : features) { - if (!feature.isSelected()) { - return false; - } - } - - return true; - } - -} diff --git a/src/main/java/com/skcraft/launcher/model/modpack/RequireAny.java b/src/main/java/com/skcraft/launcher/model/modpack/RequireAny.java deleted file mode 100644 index 299595a..0000000 --- a/src/main/java/com/skcraft/launcher/model/modpack/RequireAny.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.model.modpack; - -import lombok.Data; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@Data -public class RequireAny implements Condition { - - private List features = new ArrayList(); - - public RequireAny() { - } - - public RequireAny(List features) { - this.features = features; - } - - public RequireAny(Feature... feature) { - features.addAll(Arrays.asList(feature)); - } - - @Override - public boolean matches() { - if (features == null) { - return true; - } - - for (Feature feature : features) { - if (feature.isSelected()) { - return true; - } - } - - return false; - } - -} diff --git a/src/main/java/com/skcraft/launcher/persistence/MkdirByteSink.java b/src/main/java/com/skcraft/launcher/persistence/MkdirByteSink.java deleted file mode 100644 index e76be6c..0000000 --- a/src/main/java/com/skcraft/launcher/persistence/MkdirByteSink.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.persistence; - -import com.google.common.io.ByteSink; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; - -class MkdirByteSink extends ByteSink { - - private final ByteSink delegate; - private final File dir; - - public MkdirByteSink(ByteSink delegate, File dir) { - this.delegate = delegate; - this.dir = dir; - } - - @Override - public OutputStream openStream() throws IOException { - dir.mkdirs(); - return delegate.openStream(); - } - -} diff --git a/src/main/java/com/skcraft/launcher/persistence/Persistence.java b/src/main/java/com/skcraft/launcher/persistence/Persistence.java deleted file mode 100644 index c6580a8..0000000 --- a/src/main/java/com/skcraft/launcher/persistence/Persistence.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.persistence; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.io.ByteSink; -import com.google.common.io.ByteSource; -import com.google.common.io.Closer; -import com.google.common.io.Files; -import lombok.NonNull; -import lombok.extern.java.Log; - -import java.io.*; -import java.util.WeakHashMap; -import java.util.logging.Level; - -/** - * Simple persistence framework that can read an object from a file, bind - * that object to that file, and allow any code having a reference to the - * object make changes to the object and save those changes back to disk. - *

- * For example: - *
config = Persistence.load(file, Configuration.class);
- * config.changeSomething();
- * Persistence.commit(config);
- */ -@Log -public final class Persistence { - - private static final ObjectMapper mapper = new ObjectMapper(); - private static final WeakHashMap bound = - new WeakHashMap(); - - private Persistence() { - } - - /** - * Bind an object to a path where the object will be saved. - * - * @param object the object - * @param sink the byte sink - */ - public static void bind(@NonNull Object object, @NonNull ByteSink sink) { - synchronized (bound) { - bound.put(object, sink); - } - } - - /** - * Save an object to file. - * - * @param object the object - * @throws java.io.IOException on save error - */ - public static void commit(@NonNull Object object) throws IOException { - ByteSink sink; - synchronized (bound) { - sink = bound.get(object); - if (sink == null) { - throw new IOException("Cannot persist unbound object: " + object); - } - } - - Closer closer = Closer.create(); - try { - OutputStream os = closer.register(sink.openBufferedStream()); - mapper.writeValue(os, object); - } finally { - closer.close(); - } - } - - /** - * Save an object to file, and send all errors to the log. - * - * @param object the object - */ - public static void commitAndForget(@NonNull Object object) { - try { - commit(object); - } catch (IOException e) { - log.log(Level.WARNING, "Failed to save " + object.getClass() + ": " + object.toString(), e); - } - } - - /** - * Read an object from a byte source, without binding it. - * - * @param source byte source - * @param cls the class - * @param returnNull true to return null if the object could not be loaded - * @param the type of class - * @return an object - */ - public static V read(ByteSource source, Class cls, boolean returnNull) { - V object; - Closer closer = Closer.create(); - - try { - object = mapper.readValue(closer.register(source.openBufferedStream()), cls); - } catch (IOException e) { - if (!(e instanceof FileNotFoundException)) { - log.log(Level.INFO, "Failed to load" + cls.getCanonicalName(), e); - } - - if (returnNull) { - return null; - } - - try { - object = cls.newInstance(); - } catch (InstantiationException e1) { - throw new RuntimeException( - "Failed to construct object with no-arg constructor", e1); - } catch (IllegalAccessException e1) { - throw new RuntimeException( - "Failed to construct object with no-arg constructor", e1); - } - } finally { - try { - closer.close(); - } catch (IOException e) { - } - } - - return object; - } - - /** - * Read an object from file, without binding it. - * - * @param file the file - * @param cls the class - * @param returnNull true to return null if the object could not be loaded - * @param the type of class - * @return an object - */ - public static V read(File file, Class cls, boolean returnNull) { - return read(Files.asByteSource(file), cls, returnNull); - } - - - /** - * Read an object from file, without binding it. - * - * @param file the file - * @param cls the class - * @param the type of class - * @return an object - */ - public static V read(File file, Class cls) { - return read(file, cls, false); - } - - /** - * Read an object from file. - * - * @param file the file - * @param cls the class - * @param returnNull true to return null if the object could not be loaded - * @param the type of class - * @return an object - */ - public static V load(File file, Class cls, boolean returnNull) { - ByteSource source = Files.asByteSource(file); - ByteSink sink = new MkdirByteSink(Files.asByteSink(file), file.getParentFile()); - - Scrambled scrambled = cls.getAnnotation(Scrambled.class); - if (cls.getAnnotation(Scrambled.class) != null) { - source = new ScramblingSourceFilter(source, scrambled.value()); - sink = new ScramblingSinkFilter(sink, scrambled.value()); - } - - V object = read(source, cls, returnNull); - Persistence.bind(object, sink); - return object; - } - - /** - * Read an object from file. - * - *

If the file does not exist or loading fails, construct a new instance of - * the given class by using its no-arg constructor.

- * - * @param file the file - * @param cls the class - * @param the type of class - * @return an object - */ - public static V load(File file, Class cls) { - return load(file, cls, false); - } - - /** - * Write an object to file. - * - * @param file the file - * @param object the object - * @throws java.io.IOException on I/O error - */ - public static void write(File file, Object object) throws IOException { - file.getParentFile().mkdirs(); - mapper.writeValue(file, object); - } - -} diff --git a/src/main/java/com/skcraft/launcher/persistence/Scrambled.java b/src/main/java/com/skcraft/launcher/persistence/Scrambled.java deleted file mode 100644 index 672bbb0..0000000 --- a/src/main/java/com/skcraft/launcher/persistence/Scrambled.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.persistence; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Classes that are annotated with this will be saved scrambled - * to disk when saved using {@link com.skcraft.launcher.persistence.Persistence}. - *

- * The data may be scrambled using an encryption algorithm, but it's not - * done with security in mind. Decryption requires a key, and that - * key would either have to be stored in the source code, defeating the - * purpose of encryption, or the user would have to be prompted with a - * password every time (possibly through an OS key ring service). - * That creates extra hassle, so it is not done here. - *

- * Therefore , you should not depend on data that is scrambled to - * be secure. It does, however, make it impossible for most people - * to just read the file's contents, which is "better than nothing" - * Documentation should not indicate to the user that the data is - * protected however, because that would provide a false sense of - * security. - *

- * Account data is scrambled to make it harder to extract passwords. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface Scrambled { - - /** - * A key used in scrambling. - *

- * The key should not change once deployed. - * - * @return a key - */ - String value(); - -} diff --git a/src/main/java/com/skcraft/launcher/persistence/ScramblingSinkFilter.java b/src/main/java/com/skcraft/launcher/persistence/ScramblingSinkFilter.java deleted file mode 100644 index 862a6e9..0000000 --- a/src/main/java/com/skcraft/launcher/persistence/ScramblingSinkFilter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.persistence; - -import com.google.common.io.ByteSink; - -import javax.crypto.*; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.PBEParameterSpec; -import java.io.IOException; -import java.io.OutputStream; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import java.util.Random; - -class ScramblingSinkFilter extends ByteSink { - - private final ByteSink delegate; - private final String key; - - public ScramblingSinkFilter(ByteSink delegate, String key) { - this.delegate = delegate; - this.key = key; - } - - @Override - public OutputStream openStream() throws IOException { - Cipher cipher = null; - try { - cipher = getCipher(Cipher.ENCRYPT_MODE, key); - } catch (Throwable e) { - throw new IOException("Failed to create cipher", e); - } - return new CipherOutputStream(delegate.openStream(), cipher); - } - - public static Cipher getCipher(int mode, String password) - throws InvalidKeySpecException, NoSuchAlgorithmException, - NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException { - // These parameters were used for encrypting lastlogin on old official Minecraft launchers - Random random = new Random(0x29482c2L); - byte salt[] = new byte[8]; - random.nextBytes(salt); - PBEParameterSpec paramSpec = new PBEParameterSpec(salt, 5); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); - SecretKey key = factory.generateSecret(new PBEKeySpec(password.toCharArray())); - Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES"); - cipher.init(mode, key, paramSpec); - return cipher; - } - -} diff --git a/src/main/java/com/skcraft/launcher/persistence/ScramblingSourceFilter.java b/src/main/java/com/skcraft/launcher/persistence/ScramblingSourceFilter.java deleted file mode 100644 index fdfa03b..0000000 --- a/src/main/java/com/skcraft/launcher/persistence/ScramblingSourceFilter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.persistence; - -import com.google.common.io.ByteSource; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import java.io.IOException; -import java.io.InputStream; - -class ScramblingSourceFilter extends ByteSource { - - private final ByteSource delegate; - private final String key; - - public ScramblingSourceFilter(ByteSource delegate, String key) { - this.delegate = delegate; - this.key = key; - } - - @Override - public InputStream openStream() throws IOException { - Cipher cipher = null; - try { - cipher = ScramblingSinkFilter.getCipher(Cipher.DECRYPT_MODE, key); - } catch (Throwable e) { - throw new IOException("Failed to create cipher", e); - } - return new CipherInputStream(delegate.openStream(), cipher); - } - -} diff --git a/src/main/java/com/skcraft/launcher/selfupdate/ComparableVersion.java b/src/main/java/com/skcraft/launcher/selfupdate/ComparableVersion.java deleted file mode 100644 index 1adcf30..0000000 --- a/src/main/java/com/skcraft/launcher/selfupdate/ComparableVersion.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package com.skcraft.launcher.selfupdate; - -import java.util.*; - -/** - * Generic implementation of version comparison. - *

- * NOTE: This class is a copy of r658725 of http://svn.apache.org/repos/asf/maven/artifact/trunk/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java. - * - * @author Kenney Westerhof - * @author Herve Boutemy - * @version $Id$ - */ -@SuppressWarnings("unchecked") -public class ComparableVersion - implements Comparable { - private String value; - - private String canonical; - - private ListItem items; - - private interface Item { - public static final int INTEGER_ITEM = 0; - public static final int STRING_ITEM = 1; - public static final int LIST_ITEM = 2; - - public int compareTo(Item item); - - public int getType(); - - public boolean isNull(); - } - - /** - * Represents a numeric item in the version item list. - */ - private static class IntegerItem - implements Item { - private Integer value; - - public IntegerItem(Integer i) { - this.value = i; - } - - public int getType() { - return INTEGER_ITEM; - } - - public boolean isNull() { - return (value == 0); - } - - public int compareTo(Item item) { - if (item == null) { - return value == 0 ? 0 : 1; // 1.0 == 1, 1.1 > 1 - } - - switch (item.getType()) { - case INTEGER_ITEM: - return value.compareTo(((IntegerItem) item).value); - - case STRING_ITEM: - return 1; // 1.1 > 1-sp - - case LIST_ITEM: - return 1; // 1.1 > 1-1 - - default: - throw new RuntimeException("invalid item: " + item.getClass()); - } - } - - public String toString() { - return value.toString(); - } - } - - /** - * Represents a string in the version item list, usually a qualifier. - */ - private static class StringItem - implements Item { - private final static String[] QUALIFIERS = {"snapshot", "alpha", "beta", "milestone", "rc", "", "sp"}; - - private final static List _QUALIFIERS = Arrays.asList(QUALIFIERS); - - private final static Properties ALIASES = new Properties(); - - static { - ALIASES.put("ga", ""); - ALIASES.put("final", ""); - ALIASES.put("cr", "rc"); - } - - /** - * A comparable for the empty-string qualifier. This one is used to determine if a given qualifier makes the - * version older than one without a qualifier, or more recent. - */ - private static Comparable RELEASE_VERSION_INDEX = String.valueOf(_QUALIFIERS.indexOf("")); - - private String value; - - public StringItem(String value, boolean followedByDigit) { - if (followedByDigit && value.length() == 1) { - // a1 = alpha-1, b1 = beta-1, m1 = milestone-1 - switch (value.charAt(0)) { - case 'a': - value = "alpha"; - break; - case 'b': - value = "beta"; - break; - case 'm': - value = "milestone"; - break; - } - } - this.value = ALIASES.getProperty(value, value); - } - - public int getType() { - return STRING_ITEM; - } - - public boolean isNull() { - return (comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX) == 0); - } - - /** - * Returns a comparable for a qualifier. - *

- * This method both takes into account the ordering of known qualifiers as well as lexical ordering for unknown - * qualifiers. - *

- * just returning an Integer with the index here is faster, but requires a lot of if/then/else to check for -1 - * or QUALIFIERS.size and then resort to lexical ordering. Most comparisons are decided by the first character, - * so this is still fast. If more characters are needed then it requires a lexical sort anyway. - * - * @param qualifier - * @return - */ - public static Comparable comparableQualifier(String qualifier) { - int i = _QUALIFIERS.indexOf(qualifier); - - return i == -1 ? _QUALIFIERS.size() + "-" + qualifier : String.valueOf(i); - } - - public int compareTo(Item item) { - if (item == null) { - // 1-rc < 1, 1-ga > 1 - return comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX); - } - switch (item.getType()) { - case INTEGER_ITEM: - return -1; // 1.any < 1.1 ? - - case STRING_ITEM: - return comparableQualifier(value).compareTo(comparableQualifier(((StringItem) item).value)); - - case LIST_ITEM: - return -1; // 1.any < 1-1 - - default: - throw new RuntimeException("invalid item: " + item.getClass()); - } - } - - public String toString() { - return value; - } - } - - /** - * Represents a version list item. This class is used both for the global item list and for sub-lists (which start - * with '-(number)' in the version specification). - */ - private static class ListItem - extends ArrayList - implements Item { - public int getType() { - return LIST_ITEM; - } - - public boolean isNull() { - return (size() == 0); - } - - void normalize() { - for (ListIterator iterator = listIterator(size()); iterator.hasPrevious(); ) { - Item item = (Item) iterator.previous(); - if (item.isNull()) { - iterator.remove(); // remove null trailing items: 0, "", empty list - } else { - break; - } - } - } - - public int compareTo(Item item) { - if (item == null) { - if (size() == 0) { - return 0; // 1-0 = 1- (normalize) = 1 - } - Item first = (Item) get(0); - return first.compareTo(null); - } - switch (item.getType()) { - case INTEGER_ITEM: - return -1; // 1-1 < 1.0.x - - case STRING_ITEM: - return 1; // 1-1 > 1-sp - - case LIST_ITEM: - Iterator left = iterator(); - Iterator right = ((ListItem) item).iterator(); - - while (left.hasNext() || right.hasNext()) { - Item l = left.hasNext() ? (Item) left.next() : null; - Item r = right.hasNext() ? (Item) right.next() : null; - - // if this is shorter, then invert the compare and mul with -1 - int result = l == null ? -1 * r.compareTo(l) : l.compareTo(r); - - if (result != 0) { - return result; - } - } - - return 0; - - default: - throw new RuntimeException("invalid item: " + item.getClass()); - } - } - - public String toString() { - StringBuffer buffer = new StringBuffer("("); - for (Iterator iter = iterator(); iter.hasNext(); ) { - buffer.append(iter.next()); - if (iter.hasNext()) { - buffer.append(','); - } - } - buffer.append(')'); - return buffer.toString(); - } - } - - public ComparableVersion(String version) { - parseVersion(version); - } - - public final void parseVersion(String version) { - this.value = version; - - items = new ListItem(); - - version = version.toLowerCase(Locale.ENGLISH); - - ListItem list = items; - - Stack stack = new Stack(); - stack.push(list); - - boolean isDigit = false; - - int startIndex = 0; - - for (int i = 0; i < version.length(); i++) { - char c = version.charAt(i); - - if (c == '.') { - if (i == startIndex) { - list.add(new IntegerItem(0)); - } else { - list.add(parseItem(isDigit, version.substring(startIndex, i))); - } - startIndex = i + 1; - } else if (c == '-') { - if (i == startIndex) { - list.add(new IntegerItem(0)); - } else { - list.add(parseItem(isDigit, version.substring(startIndex, i))); - } - startIndex = i + 1; - - if (isDigit) { - list.normalize(); // 1.0-* = 1-* - - if ((i + 1 < version.length()) && Character.isDigit(version.charAt(i + 1))) { - // new ListItem only if previous were digits and new char is a digit, - // ie need to differentiate only 1.1 from 1-1 - list.add(list = new ListItem()); - - stack.push(list); - } - } - } else if (Character.isDigit(c)) { - if (!isDigit && i > startIndex) { - list.add(new StringItem(version.substring(startIndex, i), true)); - startIndex = i; - } - - isDigit = true; - } else { - if (isDigit && i > startIndex) { - list.add(parseItem(true, version.substring(startIndex, i))); - startIndex = i; - } - - isDigit = false; - } - } - - if (version.length() > startIndex) { - list.add(parseItem(isDigit, version.substring(startIndex))); - } - - while (!stack.isEmpty()) { - list = (ListItem) stack.pop(); - list.normalize(); - } - - canonical = items.toString(); - } - - private static Item parseItem(boolean isDigit, String buf) { - return isDigit ? new IntegerItem(new Integer(buf)) : new StringItem(buf, false); - } - - public int compareTo(Object o) { - return items.compareTo(((ComparableVersion) o).items); - } - - public String toString() { - return value; - } - - public boolean equals(Object o) { - return (o instanceof ComparableVersion) && canonical.equals(((ComparableVersion) o).canonical); - } - - public int hashCode() { - return canonical.hashCode(); - } -} \ No newline at end of file diff --git a/src/main/java/com/skcraft/launcher/selfupdate/LatestVersionInfo.java b/src/main/java/com/skcraft/launcher/selfupdate/LatestVersionInfo.java deleted file mode 100644 index b299a3c..0000000 --- a/src/main/java/com/skcraft/launcher/selfupdate/LatestVersionInfo.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.selfupdate; - -import lombok.Data; - -import java.net.URL; - -@Data -public class LatestVersionInfo { - - private String version; - private URL url; - -} diff --git a/src/main/java/com/skcraft/launcher/selfupdate/SelfUpdater.java b/src/main/java/com/skcraft/launcher/selfupdate/SelfUpdater.java deleted file mode 100644 index 92af33c..0000000 --- a/src/main/java/com/skcraft/launcher/selfupdate/SelfUpdater.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.selfupdate; - -import com.skcraft.concurrency.DefaultProgress; -import com.skcraft.concurrency.ProgressObservable; -import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.install.FileMover; -import com.skcraft.launcher.install.Installer; -import lombok.NonNull; - -import java.io.File; -import java.net.URL; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import static com.skcraft.launcher.util.SharedLocale._; - -public class SelfUpdater implements Callable, ProgressObservable { - - public static boolean updatedAlready = false; - - private final Launcher launcher; - private final URL url; - private final Installer installer; - private ProgressObservable progress = new DefaultProgress(0, _("updater.updating")); - - public SelfUpdater(@NonNull Launcher launcher, @NonNull URL url) { - this.launcher = launcher; - this.url = url; - this.installer = new Installer(launcher.getInstallerDir()); - } - - @Override - public File call() throws Exception { - ExecutorService executor = Executors.newSingleThreadExecutor(); - - try { - File dir = launcher.getLauncherBinariesDir(); - File file = new File(dir, String.valueOf(System.currentTimeMillis()) + ".jar.pack"); - File tempFile = installer.getDownloader().download(url, "", 10000, "launcher.jar.pack"); - - progress = installer.getDownloader(); - installer.download(); - - installer.queue(new FileMover(tempFile, file)); - - progress = installer; - installer.execute(); - - updatedAlready = true; - - return file; - } finally { - executor.shutdownNow(); - } - } - - @Override - public double getProgress() { - return progress.getProgress(); - } - - @Override - public String getStatus() { - return progress.getStatus(); - } - -} diff --git a/src/main/java/com/skcraft/launcher/selfupdate/UpdateChecker.java b/src/main/java/com/skcraft/launcher/selfupdate/UpdateChecker.java deleted file mode 100644 index f0e8517..0000000 --- a/src/main/java/com/skcraft/launcher/selfupdate/UpdateChecker.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.selfupdate; - -import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.LauncherException; -import com.skcraft.launcher.util.HttpRequest; -import lombok.NonNull; -import lombok.extern.java.Log; - -import java.net.URL; -import java.util.concurrent.Callable; - -import static com.skcraft.launcher.util.SharedLocale._; - -/** - * A worker that checks for an update to the launcher. A URL is returned - * if there is an update to be downloaded. - */ -@Log -public class UpdateChecker implements Callable { - - private final Launcher launcher; - - public UpdateChecker(@NonNull Launcher launcher) { - this.launcher = launcher; - } - - @Override - public URL call() throws Exception { - try { - UpdateChecker.log.info("Checking for update..."); - - URL url = HttpRequest.url(launcher.getProperties().getProperty("selfUpdateUrl")); - - LatestVersionInfo versionInfo = HttpRequest.get(url) - .execute() - .expectResponseCode(200) - .returnContent() - .asJson(LatestVersionInfo.class); - - ComparableVersion current = new ComparableVersion(launcher.getVersion()); - ComparableVersion latest = new ComparableVersion(versionInfo.getVersion()); - - UpdateChecker.log.info("Latest version is " + latest + ", while current is " + current); - - if (latest.compareTo(current) >= 1) { - UpdateChecker.log.info("Update available at " + versionInfo.getUrl()); - return versionInfo.getUrl(); - } else { - UpdateChecker.log.info("No update required."); - return null; - } - } catch (Exception e) { - throw new LauncherException(e, _("errors.selfUpdateCheckError")); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/ActionListeners.java b/src/main/java/com/skcraft/launcher/swing/ActionListeners.java deleted file mode 100644 index 807d3ae..0000000 --- a/src/main/java/com/skcraft/launcher/swing/ActionListeners.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; - -/** - * Utility method to make {@link ActionListeners}. - */ -public final class ActionListeners { - - private ActionListeners() { - } - - public static ActionListener dispose(final Window window) { - return new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - window.dispose(); - } - }; - } - - public static ActionListener openURL(final Component component, final String url) { - return new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - SwingHelper.openURL(url, component); - } - }; - } - - public static ActionListener browseDir( - final Component component, final File dir, final boolean create) { - return new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (create) { - dir.mkdirs(); - } - SwingHelper.browseDir(dir, component); - } - }; - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/CheckboxTable.java b/src/main/java/com/skcraft/launcher/swing/CheckboxTable.java deleted file mode 100644 index 797ff05..0000000 --- a/src/main/java/com/skcraft/launcher/swing/CheckboxTable.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import javax.swing.*; -import javax.swing.table.TableModel; -import java.awt.*; - -public class CheckboxTable extends JTable { - - public CheckboxTable() { - setShowGrid(false); - setRowHeight((int) (Math.max(getRowHeight(), new JCheckBox().getPreferredSize().getHeight() - 2))); - setIntercellSpacing(new Dimension(0, 0)); - setFillsViewportHeight(true); - setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - } - - @Override - public void setModel(TableModel dataModel) { - super.setModel(dataModel); - try { - getColumnModel().getColumn(0).setMaxWidth((int) new JCheckBox().getPreferredSize().getWidth()); - } catch (ArrayIndexOutOfBoundsException e) { - } - } -} diff --git a/src/main/java/com/skcraft/launcher/swing/DoubleClickToButtonAdapter.java b/src/main/java/com/skcraft/launcher/swing/DoubleClickToButtonAdapter.java deleted file mode 100644 index 5c6a841..0000000 --- a/src/main/java/com/skcraft/launcher/swing/DoubleClickToButtonAdapter.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import javax.swing.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -public class DoubleClickToButtonAdapter extends MouseAdapter { - - private final AbstractButton button; - - public DoubleClickToButtonAdapter(AbstractButton button) { - this.button = button; - } - - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2) { - button.doClick(); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/FeatureTableModel.java b/src/main/java/com/skcraft/launcher/swing/FeatureTableModel.java deleted file mode 100644 index 8e4b873..0000000 --- a/src/main/java/com/skcraft/launcher/swing/FeatureTableModel.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import com.skcraft.launcher.model.modpack.Feature; - -import javax.swing.table.AbstractTableModel; -import java.util.List; - -import static com.skcraft.launcher.util.SharedLocale._; - -public class FeatureTableModel extends AbstractTableModel { - - private final List features; - - public FeatureTableModel(List features) { - this.features = features; - } - - @Override - public String getColumnName(int columnIndex) { - switch (columnIndex) { - case 1: - return _("features.nameColumn"); - default: - return null; - } - } - - @Override - public Class getColumnClass(int columnIndex) { - switch (columnIndex) { - case 0: - return Boolean.class; - case 1: - return String.class; - default: - return null; - } - } - - @Override - public void setValueAt(Object value, int rowIndex, int columnIndex) { - switch (columnIndex) { - case 0: - features.get(rowIndex).setSelected((boolean) (Boolean) value); - break; - case 1: - default: - break; - } - } - - @Override - public boolean isCellEditable(int rowIndex, int columnIndex) { - switch (columnIndex) { - case 0: - return true; - case 1: - return false; - default: - return false; - } - } - - @Override - public int getRowCount() { - return features.size(); - } - - @Override - public int getColumnCount() { - return 2; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - switch (columnIndex) { - case 0: - return features.get(rowIndex).isSelected(); - case 1: - Feature feature = features.get(rowIndex); - return "" + SwingHelper.htmlEscape(feature.getName()) + getAddendum(feature) + ""; - default: - return null; - } - } - - private String getAddendum(Feature feature) { - if (feature.getRecommendation() == null) { - return ""; - } - switch (feature.getRecommendation()) { - case STARRED: - return " " + _("features.starred") + ""; - case AVOID: - return " " + _("features.avoid") + ""; - default: - return ""; - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/FormPanel.java b/src/main/java/com/skcraft/launcher/swing/FormPanel.java deleted file mode 100644 index 5808227..0000000 --- a/src/main/java/com/skcraft/launcher/swing/FormPanel.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import javax.swing.*; -import java.awt.*; - -public class FormPanel extends JPanel { - - private static final GridBagConstraints labelConstraints; - private static final GridBagConstraints fieldConstraints; - private static final GridBagConstraints wideFieldConstraints; - - private final GridBagLayout layout; - - static { - fieldConstraints = new GridBagConstraints(); - fieldConstraints.fill = GridBagConstraints.HORIZONTAL; - fieldConstraints.weightx = 1.0; - fieldConstraints.gridwidth = GridBagConstraints.REMAINDER; - fieldConstraints.insets = new Insets(5, 5, 2, 5); - - labelConstraints = (GridBagConstraints) fieldConstraints.clone(); - labelConstraints.weightx = 0.0; - labelConstraints.gridwidth = 1; - labelConstraints.insets = new Insets(4, 5, 1, 10); - - wideFieldConstraints = (GridBagConstraints) fieldConstraints.clone(); - wideFieldConstraints.insets = new Insets(7, 2, 1, 2); - } - - public FormPanel() { - setLayout(layout = new GridBagLayout()); - setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - } - - public void addRow(Component label, Component component) { - add(label); - add(component); - layout.setConstraints(label, labelConstraints); - layout.setConstraints(component, fieldConstraints); - } - - public void addRow(Component component) { - add(component); - layout.setConstraints(component, wideFieldConstraints); - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/HeaderPanel.java b/src/main/java/com/skcraft/launcher/swing/HeaderPanel.java deleted file mode 100644 index da04fa3..0000000 --- a/src/main/java/com/skcraft/launcher/swing/HeaderPanel.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import javax.swing.*; -import java.awt.*; - -public class HeaderPanel extends JPanel { - - public HeaderPanel() { - setBackground(new Color(0xDB5036)); - } - - @Override - public Dimension getPreferredSize() { - return new Dimension(200, 60); - } - - @Override - public void paint(Graphics g) { - super.paint(g); - } -} diff --git a/src/main/java/com/skcraft/launcher/swing/InstanceTable.java b/src/main/java/com/skcraft/launcher/swing/InstanceTable.java deleted file mode 100644 index 85fdce0..0000000 --- a/src/main/java/com/skcraft/launcher/swing/InstanceTable.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import javax.swing.*; -import javax.swing.table.TableModel; -import java.awt.*; - -public class InstanceTable extends JTable { - - public InstanceTable() { - setShowGrid(false); - setRowHeight(Math.max(getRowHeight() + 4, 20)); - setIntercellSpacing(new Dimension(0, 0)); - setFillsViewportHeight(true); - setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - } - - @Override - public void setModel(TableModel dataModel) { - super.setModel(dataModel); - try { - getColumnModel().getColumn(0).setMaxWidth(24); - } catch (ArrayIndexOutOfBoundsException e) { - } - } -} diff --git a/src/main/java/com/skcraft/launcher/swing/InstanceTableModel.java b/src/main/java/com/skcraft/launcher/swing/InstanceTableModel.java deleted file mode 100644 index 03887bf..0000000 --- a/src/main/java/com/skcraft/launcher/swing/InstanceTableModel.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import com.skcraft.launcher.Instance; -import com.skcraft.launcher.InstanceList; -import com.skcraft.launcher.Launcher; - -import javax.swing.*; -import javax.swing.table.AbstractTableModel; -import java.awt.*; - -import static com.skcraft.launcher.util.SharedLocale._; - -public class InstanceTableModel extends AbstractTableModel { - - private final InstanceList instances; - private final ImageIcon instanceIcon; - private final ImageIcon customInstanceIcon; - private final ImageIcon downloadIcon; - - public InstanceTableModel(InstanceList instances) { - this.instances = instances; - instanceIcon = new ImageIcon(SwingHelper.readIconImage(Launcher.class, "instance_icon.png") - .getScaledInstance(16, 16, Image.SCALE_SMOOTH)); - customInstanceIcon = new ImageIcon(SwingHelper.readIconImage(Launcher.class, "custom_instance_icon.png") - .getScaledInstance(16, 16, Image.SCALE_SMOOTH)); - downloadIcon = new ImageIcon(SwingHelper.readIconImage(Launcher.class, "download_icon.png") - .getScaledInstance(14, 14, Image.SCALE_SMOOTH)); - } - - public void update() { - instances.sort(); - fireTableDataChanged(); - } - - @Override - public String getColumnName(int columnIndex) { - switch (columnIndex) { - case 0: - return ""; - case 1: - return _("launcher.modpackColumn"); - default: - return null; - } - } - - @Override - public Class getColumnClass(int columnIndex) { - switch (columnIndex) { - case 0: - return ImageIcon.class; - case 1: - return String.class; - default: - return null; - } - } - - @Override - public void setValueAt(Object value, int rowIndex, int columnIndex) { - switch (columnIndex) { - case 0: - instances.get(rowIndex).setSelected((boolean) (Boolean) value); - break; - case 1: - default: - break; - } - } - - @Override - public boolean isCellEditable(int rowIndex, int columnIndex) { - switch (columnIndex) { - case 0: - return true; - case 1: - return false; - default: - return false; - } - } - - @Override - public int getRowCount() { - return instances.size(); - } - - @Override - public int getColumnCount() { - return 2; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - Instance instance; - switch (columnIndex) { - case 0: - instance = instances.get(rowIndex); - if (!instance.isLocal()) { - return downloadIcon; - } else if (instance.getManifestURL() != null) { - return instanceIcon; - } else { - return customInstanceIcon; - } - case 1: - instance = instances.get(rowIndex); - return "" + SwingHelper.htmlEscape(instance.getTitle()) + getAddendum(instance) + ""; - default: - return null; - } - } - - private String getAddendum(Instance instance) { - if (!instance.isLocal()) { - return " " + _("launcher.notInstalledHint") + ""; - } else if (!instance.isInstalled()) { - return " " + _("launcher.requiresUpdateHint") + ""; - } else if (instance.isUpdatePending()) { - return " " + _("launcher.updatePendingHint") + ""; - } else { - return ""; - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/LinedBoxPanel.java b/src/main/java/com/skcraft/launcher/swing/LinedBoxPanel.java deleted file mode 100644 index cb61e11..0000000 --- a/src/main/java/com/skcraft/launcher/swing/LinedBoxPanel.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import lombok.Getter; -import lombok.Setter; - -import javax.swing.*; -import java.awt.*; - -public class LinedBoxPanel extends JPanel { - - @Getter - private final boolean horizontal; - @Getter @Setter - private int spacing = 6; - private boolean needsSpacer = false; - - public LinedBoxPanel(boolean horizontal) { - this.horizontal = horizontal; - setLayout(new BoxLayout(this, - horizontal ? BoxLayout.X_AXIS : BoxLayout.Y_AXIS)); - setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); - } - - public LinedBoxPanel fullyPadded() { - setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - return this; - } - - public void addElement(Component component) { - if (needsSpacer) { - add(horizontal ? - Box.createHorizontalStrut(spacing) : - Box.createVerticalStrut(spacing)); - } - add(component); - needsSpacer = true; - } - - public void addGlue() { - add(horizontal ? - Box.createHorizontalGlue() : - Box.createVerticalGlue()); - needsSpacer = false; - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/LinkButton.java b/src/main/java/com/skcraft/launcher/swing/LinkButton.java deleted file mode 100644 index 30c45fb..0000000 --- a/src/main/java/com/skcraft/launcher/swing/LinkButton.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import javax.swing.*; -import javax.swing.border.Border; -import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -public class LinkButton extends JButton { - - private static final Color LINK_COLOR = Color.blue; - private static final Border LINK_BORDER = BorderFactory.createEmptyBorder(0, 0, 1, 0); - private static final Border HOVER_BORDER = BorderFactory.createMatteBorder(0, 0, 1, 0, LINK_COLOR); - - public LinkButton() { - super(); - setupLink(); - } - - public LinkButton(Action a) { - super(a); - setupLink(); - } - - public LinkButton(Icon icon) { - super(icon); - setupLink(); - } - - public LinkButton(String text, Icon icon) { - super(text, icon); - setupLink(); - } - - public LinkButton(String text) { - super(text); - setupLink(); - } - - public void setupLink() { - setBorder(LINK_BORDER); - setForeground(LINK_COLOR); - setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - setFocusPainted(false); - setRequestFocusEnabled(false); - setContentAreaFilled(false); - addMouseListener(new MouseAdapter() { - @Override - public void mouseEntered(MouseEvent e) { - ((JComponent) e.getComponent()).setBorder(HOVER_BORDER); - } - - @Override - public void mouseReleased(MouseEvent e) { - ((JComponent) e.getComponent()).setBorder(LINK_BORDER); - } - - @Override - public void mouseExited(MouseEvent e) { - ((JComponent) e.getComponent()).setBorder(LINK_BORDER); - } - }); - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/MessageLog.java b/src/main/java/com/skcraft/launcher/swing/MessageLog.java deleted file mode 100644 index ccc877a..0000000 --- a/src/main/java/com/skcraft/launcher/swing/MessageLog.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import com.skcraft.launcher.LauncherUtils; -import com.skcraft.launcher.util.LimitLinesDocumentListener; -import com.skcraft.launcher.util.SimpleLogFormatter; - -import javax.swing.*; -import javax.swing.text.*; -import java.awt.*; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; - -import static org.apache.commons.io.IOUtils.closeQuietly; - -/** - * A simple message log. - */ -public class MessageLog extends JPanel { - - private static final Logger rootLogger = Logger.getLogger(""); - - private final int numLines; - private final boolean colorEnabled; - - protected JTextComponent textComponent; - protected Document document; - - private Handler loggerHandler; - protected final SimpleAttributeSet defaultAttributes = new SimpleAttributeSet(); - protected final SimpleAttributeSet highlightedAttributes; - protected final SimpleAttributeSet errorAttributes; - protected final SimpleAttributeSet infoAttributes; - protected final SimpleAttributeSet debugAttributes; - - public MessageLog(int numLines, boolean colorEnabled) { - this.numLines = numLines; - this.colorEnabled = colorEnabled; - - this.highlightedAttributes = new SimpleAttributeSet(); - StyleConstants.setForeground(highlightedAttributes, new Color(0xFF7F00)); - - this.errorAttributes = new SimpleAttributeSet(); - StyleConstants.setForeground(errorAttributes, new Color(0xFF0000)); - this.infoAttributes = new SimpleAttributeSet(); - this.debugAttributes = new SimpleAttributeSet(); - - setLayout(new BorderLayout()); - - initComponents(); - } - - private void initComponents() { - if (colorEnabled) { - JTextPane text = new JTextPane() { - @Override - public boolean getScrollableTracksViewportWidth() { - return true; - } - }; - this.textComponent = text; - } else { - JTextArea text = new JTextArea(); - this.textComponent = text; - text.setLineWrap(true); - text.setWrapStyleWord(true); - } - - textComponent.setFont(new JLabel().getFont()); - textComponent.setEditable(false); - textComponent.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); - DefaultCaret caret = (DefaultCaret) textComponent.getCaret(); - caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); - document = textComponent.getDocument(); - document.addDocumentListener(new LimitLinesDocumentListener(numLines, true)); - - JScrollPane scrollText = new JScrollPane(textComponent); - scrollText.setBorder(null); - scrollText.setVerticalScrollBarPolicy( - ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - scrollText.setHorizontalScrollBarPolicy( - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - - add(scrollText, BorderLayout.CENTER); - } - - public String getPastableText() { - String text = textComponent.getText().replaceAll("[\r\n]+", "\n"); - text = text.replaceAll("Session ID is [A-Fa-f0-9]+", "Session ID is [redacted]"); - return text; - } - - public void clear() { - textComponent.setText(""); - } - - /** - * Log a message given the {@link javax.swing.text.AttributeSet}. - * - * @param line line - * @param attributes attribute set, or null for none - */ - public void log(String line, AttributeSet attributes) { - if (colorEnabled) { - if (line.startsWith("(!!)")) { - attributes = highlightedAttributes; - } - } - - try { - int offset = document.getLength(); - document.insertString(offset, line, - (attributes != null && colorEnabled) ? attributes : defaultAttributes); - textComponent.setCaretPosition(document.getLength()); - } catch (BadLocationException ble) { - - } - } - - /** - * Get an output stream that can be written to. - * - * @return output stream - */ - public ConsoleOutputStream getOutputStream() { - return getOutputStream((AttributeSet) null); - } - - /** - * Get an output stream with the given attribute set. - * - * @param attributes attributes - * @return output stream - */ - public ConsoleOutputStream getOutputStream(AttributeSet attributes) { - return new ConsoleOutputStream(attributes); - } - - /** - * Get an output stream using the give color. - * - * @param color color to use - * @return output stream - */ - public ConsoleOutputStream getOutputStream(Color color) { - SimpleAttributeSet attributes = new SimpleAttributeSet(); - StyleConstants.setForeground(attributes, color); - return getOutputStream(attributes); - } - - /** - * Consume an input stream and print it to the dialog. The consumer - * will be in a separate daemon thread. - * - * @param from stream to read - */ - public void consume(InputStream from) { - consume(from, getOutputStream()); - } - - /** - * Consume an input stream and print it to the dialog. The consumer - * will be in a separate daemon thread. - * - * @param from stream to read - * @param color color to use - */ - public void consume(InputStream from, Color color) { - consume(from, getOutputStream(color)); - } - - /** - * Consume an input stream and print it to the dialog. The consumer - * will be in a separate daemon thread. - * - * @param from stream to read - * @param attributes attributes - */ - public void consume(InputStream from, AttributeSet attributes) { - consume(from, getOutputStream(attributes)); - } - - /** - * Internal method to consume a stream. - * - * @param from stream to consume - * @param outputStream console stream to write to - */ - private void consume(InputStream from, ConsoleOutputStream outputStream) { - final InputStream in = from; - final PrintWriter out = new PrintWriter(outputStream, true); - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - byte[] buffer = new byte[1024]; - try { - int len; - while ((len = in.read(buffer)) != -1) { - String s = new String(buffer, 0, len); - System.out.print(s); - out.append(s); - out.flush(); - } - } catch (IOException e) { - } finally { - closeQuietly(in); - closeQuietly(out); - } - } - }); - thread.setDaemon(true); - thread.start(); - } - - /** - * Register a global logger listener. - */ - public void registerLoggerHandler() { - loggerHandler = new ConsoleLoggerHandler(); - rootLogger.addHandler(loggerHandler); - } - - /** - * Detach the handler on the global logger. - */ - public void detachGlobalHandler() { - if (loggerHandler != null) { - rootLogger.removeHandler(loggerHandler); - loggerHandler = null; - } - } - - public SimpleAttributeSet asDefault() { - return defaultAttributes; - } - - public SimpleAttributeSet asHighlighted() { - return highlightedAttributes; - } - - public SimpleAttributeSet asError() { - return errorAttributes; - } - - public SimpleAttributeSet asInfo() { - return infoAttributes; - } - - public SimpleAttributeSet asDebug() { - return debugAttributes; - } - - /** - * Used to send logger messages to the console. - */ - private class ConsoleLoggerHandler extends Handler { - private final SimpleLogFormatter formatter = new SimpleLogFormatter(); - - @Override - public void publish(LogRecord record) { - Level level = record.getLevel(); - Throwable t = record.getThrown(); - AttributeSet attributes = defaultAttributes; - - if (level.intValue() >= Level.WARNING.intValue()) { - attributes = errorAttributes; - } else if (level.intValue() < Level.INFO.intValue()) { - attributes = debugAttributes; - } - - log(formatter.format(record), attributes); - } - - @Override - public void flush() { - } - - @Override - public void close() throws SecurityException { - } - } - - /** - * Used to send console messages to the console. - */ - private class ConsoleOutputStream extends ByteArrayOutputStream { - private AttributeSet attributes; - - private ConsoleOutputStream(AttributeSet attributes) { - this.attributes = attributes; - } - - @Override - public void flush() { - String data = toString(); - if (data.length() == 0) return; - log(data, attributes); - reset(); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/ObjectSwingMapper.java b/src/main/java/com/skcraft/launcher/swing/ObjectSwingMapper.java deleted file mode 100644 index 6c4957d..0000000 --- a/src/main/java/com/skcraft/launcher/swing/ObjectSwingMapper.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import com.google.common.base.Strings; -import lombok.NonNull; - -import javax.swing.*; -import javax.swing.text.JTextComponent; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -public class ObjectSwingMapper { - - private final List mappings = new ArrayList(); - private final Object object; - - public ObjectSwingMapper(@NonNull Object object) { - this.object = object; - } - - public void copyFromObject() { - for (FieldMapping mapping : mappings) { - mapping.copyFromObject(); - } - } - - public void copyFromSwing() { - for (FieldMapping mapping : mappings) { - mapping.copyFromSwing(); - } - } - - private void add(@NonNull FieldMapping mapping) { - mappings.add(mapping); - } - - private MutatorAccessorField getField(@NonNull String field, Class clazz) { - return new MutatorAccessorField(object, field, clazz); - } - - public void map(@NonNull final JTextComponent textComponent, String name) { - final MutatorAccessorField field = getField(name, String.class); - - add(new FieldMapping() { - @Override - public void copyFromObject() { - textComponent.setText(field.get()); - } - - @SuppressWarnings("unchecked") - @Override - public void copyFromSwing() { - field.set(Strings.emptyToNull(textComponent.getText())); - } - }); - } - - public void map(@NonNull final JSpinner spinner, String name) { - final MutatorAccessorField field = getField(name, int.class); - - add(new FieldMapping() { - @Override - public void copyFromObject() { - spinner.setValue(field.get()); - } - - @SuppressWarnings("unchecked") - @Override - public void copyFromSwing() { - field.set((Integer) spinner.getValue()); - } - }); - } - - public void map(@NonNull final JCheckBox check, String name) { - final MutatorAccessorField field = getField(name, boolean.class); - - add(new FieldMapping() { - @Override - public void copyFromObject() { - check.setSelected(field.get()); - } - - @SuppressWarnings("unchecked") - @Override - public void copyFromSwing() { - field.set(check.isSelected()); - } - }); - } - - public static interface FieldMapping { - void copyFromObject(); - void copyFromSwing(); - } - - public static class MutatorAccessorField { - private final Class clazz; - private final Object object; - private final Method mutator; - private final Method accessor; - - public MutatorAccessorField(Object object, String name, Class clazz) { - this.object = object; - this.clazz = clazz; - - Method mutator = null; - Method accessor = null; - for (Method method : object.getClass().getMethods()) { - if (isAccessor(method, name)) { - accessor = method; - } else if (isMutator(method, name)) { - mutator = method; - } - } - - if (accessor == null) { - throw new NoSuchMethodError("Failed to find accessor pair on " + - object.getClass().getCanonicalName() + " for " + name); - } - - if (mutator == null) { - throw new NoSuchMethodError("Failed to find mutator pair on " + - object.getClass().getCanonicalName() + " for " + name); - } - - this.mutator = mutator; - this.accessor = accessor; - } - - private boolean isAccessor(Method method, String name) { - String methodName = method.getName(); - Class[] paramTypes = method.getParameterTypes(); - Class returnType = method.getReturnType(); - - return (methodName.equalsIgnoreCase("get" + name) || - methodName.equalsIgnoreCase("is" + name)) && - paramTypes.length == 0 && - clazz.isAssignableFrom(returnType); - } - - private boolean isMutator(Method method, String name) { - String methodName = method.getName(); - Class[] paramTypes = method.getParameterTypes(); - - return methodName.equalsIgnoreCase("set" + name) && - paramTypes.length == 1 && - paramTypes[0].isAssignableFrom(clazz); - } - - @SuppressWarnings("unchecked") - public V get() { - try { - Object value = accessor.invoke(object); - return (V) value; - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - public void set(V value) { - try { - mutator.invoke(object, value); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/PopupMouseAdapter.java b/src/main/java/com/skcraft/launcher/swing/PopupMouseAdapter.java deleted file mode 100644 index ea3f6cc..0000000 --- a/src/main/java/com/skcraft/launcher/swing/PopupMouseAdapter.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -/** - * An implementation of MouseAdapter that makes it easier to handle right click menus. - */ -public abstract class PopupMouseAdapter extends MouseAdapter { - - @Override - public void mousePressed(MouseEvent e) { - if (e.isPopupTrigger()) { - showPopup(e); - } - } - - @Override - public void mouseReleased(MouseEvent e) { - if (e.isPopupTrigger()) { - showPopup(e); - } - } - - protected abstract void showPopup(MouseEvent e); - -} diff --git a/src/main/java/com/skcraft/launcher/swing/SelectionKeeper.java b/src/main/java/com/skcraft/launcher/swing/SelectionKeeper.java deleted file mode 100644 index dd777a6..0000000 --- a/src/main/java/com/skcraft/launcher/swing/SelectionKeeper.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import lombok.NonNull; - -import javax.swing.*; -import javax.swing.event.ListDataEvent; -import javax.swing.event.ListDataListener; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -public class SelectionKeeper implements ListSelectionListener, ListDataListener { - - private final JList list; - private Object lastSelected; - - private SelectionKeeper(@NonNull JList list) { - this.list = list; - } - - public void intervalAdded(ListDataEvent e) { - list.setSelectedValue(lastSelected, true); - } - - public void intervalRemoved(ListDataEvent e) { - list.setSelectedValue(lastSelected, true); - } - - public void contentsChanged(ListDataEvent e) { - list.setSelectedValue(lastSelected, true); - } - - public void valueChanged(ListSelectionEvent e) { - if (!e.getValueIsAdjusting()) { - lastSelected = list.getSelectedValue(); - } - } - - public static void attach(@NonNull JList list) { - SelectionKeeper s = new SelectionKeeper(list); - list.addListSelectionListener(s); - list.getModel().addListDataListener(s); - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/SwingHelper.java b/src/main/java/com/skcraft/launcher/swing/SwingHelper.java deleted file mode 100644 index 7779d62..0000000 --- a/src/main/java/com/skcraft/launcher/swing/SwingHelper.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.skcraft.launcher.LauncherException; -import com.skcraft.launcher.util.SwingExecutor; -import lombok.NonNull; -import lombok.extern.java.Log; - -import javax.imageio.ImageIO; -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.plaf.basic.BasicSplitPaneDivider; -import javax.swing.plaf.basic.BasicSplitPaneUI; -import javax.swing.text.JTextComponent; -import java.awt.*; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.ClipboardOwner; -import java.awt.datatransfer.StringSelection; -import java.awt.datatransfer.Transferable; -import java.awt.image.BufferedImage; -import java.io.*; -import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.concurrent.CancellationException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; - -import static com.skcraft.launcher.util.SharedLocale._; -import static org.apache.commons.io.IOUtils.closeQuietly; - -/** - * Swing utility methods. - */ -@Log -public final class SwingHelper { - - private static final ClipboardOwner clipboardOwner = new ClipboardOwner() { - @Override - public void lostOwnership(Clipboard clipboard, Transferable contents) { - - } - }; - - private SwingHelper() { - } - - public static String htmlEscape(String str) { - return str.replace(">", ">") - .replace("<", "<") - .replace("&", "&"); - } - - public static void setClipboard(String text) { - Toolkit.getDefaultToolkit().getSystemClipboard().setContents( - new StringSelection(text), clipboardOwner); - } - - public static void browseDir(File file, Component component) { - try { - Desktop.getDesktop().open(file); - } catch (IOException e) { - JOptionPane.showMessageDialog(component, _("errors.openDirError", file.getAbsolutePath()), - _("errorTitle"), JOptionPane.ERROR_MESSAGE); - } - } - - /** - * Opens a system web browser for the given URL. - * - * @param url the URL - * @param parentComponent the component from which to show any errors - */ - public static void openURL(@NonNull String url, @NonNull Component parentComponent) { - try { - openURL(new URL(url), parentComponent); - } catch (MalformedURLException e) { - } - } - - /** - * Opens a system web browser for the given URL. - * - * @param url the URL - * @param parentComponent the component from which to show any errors - */ - public static void openURL(URL url, Component parentComponent) { - try { - Desktop.getDesktop().browse(url.toURI()); - } catch (IOException e) { - showErrorDialog(parentComponent, _("errors.openUrlError", url.toString()), _("errorTitle")); - } catch (URISyntaxException e) { - } - } - - /** - * Shows an popup error dialog, with potential extra details shown either immediately - * or available on the dialog. - * - * @param parentComponent the frame from which the dialog is displayed, otherwise - * null to use the default frame - * @param message the message to display - * @param title the title string for the dialog - * @see #showMessageDialog(java.awt.Component, String, String, String, int) for details - */ - public static void showErrorDialog(Component parentComponent, @NonNull String message, - @NonNull String title) { - showErrorDialog(parentComponent, message, title, null); - } - - /** - * Shows an popup error dialog, with potential extra details shown either immediately - * or available on the dialog. - * - * @param parentComponent the frame from which the dialog is displayed, otherwise - * null to use the default frame - * @param message the message to display - * @param title the title string for the dialog - * @param throwable the exception, or null if there is no exception to show - * @see #showMessageDialog(java.awt.Component, String, String, String, int) for details - */ - public static void showErrorDialog(Component parentComponent, @NonNull String message, - @NonNull String title, Throwable throwable) { - String detailsText = null; - - // Get a string version of the exception and use that for - // the extra details text - if (throwable != null) { - StringWriter sw = new StringWriter(); - throwable.printStackTrace(new PrintWriter(sw)); - detailsText = sw.toString(); - } - - showMessageDialog(parentComponent, - message, title, - detailsText, JOptionPane.ERROR_MESSAGE); - } - - /** - * Show a message dialog using - * {@link javax.swing.JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)}. - * - *

The dialog will be shown from the Event Dispatch Thread, regardless of the - * thread it is called from. In either case, the method will block until the - * user has closed the dialog (or dialog creation fails for whatever reason).

- * - * @param parentComponent the frame from which the dialog is displayed, otherwise - * null to use the default frame - * @param message the message to display - * @param title the title string for the dialog - * @param messageType see {@link javax.swing.JOptionPane#showMessageDialog(java.awt.Component, Object, String, int)} - * for available message types - */ - public static void showMessageDialog(final Component parentComponent, - @NonNull final String message, - @NonNull final String title, - final String detailsText, - final int messageType) { - - if (SwingUtilities.isEventDispatchThread()) { - // To force the label to wrap, convert the message to broken HTML - String htmlMessage = "
" + htmlEscape(message); - - JPanel panel = new JPanel(new BorderLayout(0, detailsText != null ? 20 : 0)); - - // Add the main message - panel.add(new JLabel(htmlMessage), BorderLayout.NORTH); - - // Add the extra details - if (detailsText != null) { - JTextArea textArea = new JTextArea(_("errors.reportErrorPreface") + detailsText); - JLabel tempLabel = new JLabel(); - textArea.setFont(tempLabel.getFont()); - textArea.setBackground(tempLabel.getBackground()); - textArea.setTabSize(2); - textArea.setEditable(false); - textArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); - - JScrollPane scrollPane = new JScrollPane(textArea); - scrollPane.setPreferredSize(new Dimension(350, 120)); - panel.add(scrollPane, BorderLayout.CENTER); - } - - JOptionPane.showMessageDialog( - parentComponent, panel, title, messageType); - } else { - // Call method again from the Event Dispatch Thread - try { - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - showMessageDialog( - parentComponent, message, title, - detailsText, messageType); - } - }); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } - } - - /** - * Asks the user a binary yes or no question. - * - * @param parentComponent the component - * @param message the message to display - * @param title the title string for the dialog - * @return whether 'yes' was selected - */ - public static boolean confirmDialog(final Component parentComponent, - @NonNull final String message, - @NonNull final String title) { - if (SwingUtilities.isEventDispatchThread()) { - return JOptionPane.showConfirmDialog( - parentComponent, message, title, JOptionPane.YES_NO_OPTION) == - JOptionPane.YES_OPTION; - } else { - // Use an AtomicBoolean to pass the result back from the - // Event Dispatcher Thread - final AtomicBoolean yesSelected = new AtomicBoolean(); - - try { - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - yesSelected.set(confirmDialog(parentComponent, title, message)); - } - }); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - - return yesSelected.get(); - } - } - - /** - * Equalize the width of the given components. - * - * @param component component - */ - public static void equalWidth(Component ... component) { - double widest = 0; - for (Component comp : component) { - Dimension dim = comp.getPreferredSize(); - if (dim.getWidth() > widest) { - widest = dim.getWidth(); - } - } - - for (Component comp : component) { - Dimension dim = comp.getPreferredSize(); - comp.setPreferredSize(new Dimension((int) widest, (int) dim.getHeight())); - } - } - - /** - * Remove all the opaqueness of the given components and child components. - * - * @param components list of components - */ - public static void removeOpaqueness(@NonNull Component ... components) { - for (Component component : components) { - if (component instanceof JComponent) { - JComponent jComponent = (JComponent) component; - jComponent.setOpaque(false); - removeOpaqueness(jComponent.getComponents()); - } - } - } - - public static BufferedImage readIconImage(Class clazz, String path) { - InputStream in = null; - try { - in = clazz.getResourceAsStream(path); - if (in != null) { - return ImageIO.read(in); - } - } catch (IOException e) { - } finally { - closeQuietly(in); - } - return null; - } - - public static void setIconImage(JFrame frame, Class clazz, String path) { - BufferedImage image = readIconImage(clazz, path); - if (image != null) { - frame.setIconImage(image); - } - } - - /** - * Focus a component. - * - *

The focus call happens in {@link javax.swing.SwingUtilities#invokeLater(Runnable)}.

- * - * @param component the component - */ - public static void focusLater(@NonNull final Component component) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if (component instanceof JTextComponent) { - ((JTextComponent) component).selectAll(); - } - component.requestFocusInWindow(); - } - }); - } - - public static void flattenJSplitPane(JSplitPane splitPane) { - splitPane.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); - BasicSplitPaneUI flatDividerSplitPaneUI = new BasicSplitPaneUI() { - @Override - public BasicSplitPaneDivider createDefaultDivider() { - return new BasicSplitPaneDivider(this) { - @Override - public void setBorder(Border b) { - } - }; - } - }; - splitPane.setUI(flatDividerSplitPaneUI); - splitPane.setBorder(null); - } - - public static void addErrorDialogCallback(final Window owner, ListenableFuture future) { - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(Object result) { - } - - @Override - public void onFailure(Throwable t) { - if (t instanceof InterruptedException || t instanceof CancellationException) { - return; - } - - String message; - if (t instanceof LauncherException) { - message = t.getLocalizedMessage(); - t = t.getCause(); - } else { - message = t.getLocalizedMessage(); - if (message == null) { - message = _("errors.genericError"); - } - } - log.log(Level.WARNING, "Task failed", t); - SwingHelper.showErrorDialog(owner, message, _("errorTitle"), t); - } - }, SwingExecutor.INSTANCE); - } - - public static Component alignTabbedPane(Component component) { - JPanel container = new JPanel(); - container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS)); - container.add(component); - container.add(new Box.Filler(new Dimension(0, 0), new Dimension(0, 10000), new Dimension(0, 10000))); - SwingHelper.removeOpaqueness(container); - return container; - } -} diff --git a/src/main/java/com/skcraft/launcher/swing/TextFieldPopupMenu.java b/src/main/java/com/skcraft/launcher/swing/TextFieldPopupMenu.java deleted file mode 100644 index 07f6257..0000000 --- a/src/main/java/com/skcraft/launcher/swing/TextFieldPopupMenu.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import javax.swing.*; -import javax.swing.text.JTextComponent; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import static com.skcraft.launcher.util.SharedLocale._; - -public class TextFieldPopupMenu extends JPopupMenu implements ActionListener { - - public static final TextFieldPopupMenu INSTANCE = new TextFieldPopupMenu(); - - private final JMenuItem cutItem; - private final JMenuItem copyItem; - private final JMenuItem pasteItem; - private final JMenuItem deleteItem; - private final JMenuItem selectAllItem; - - private TextFieldPopupMenu() { - cutItem = addMenuItem(new JMenuItem(_("context.cut"), 'T')); - copyItem = addMenuItem(new JMenuItem(_("context.copy"), 'C')); - pasteItem = addMenuItem(new JMenuItem(_("context.paste"), 'P')); - deleteItem = addMenuItem(new JMenuItem(_("context.delete"), 'D')); - addSeparator(); - selectAllItem = addMenuItem(new JMenuItem(_("context.selectAll"), 'A')); - } - - private JMenuItem addMenuItem(JMenuItem item) { - item.addActionListener(this); - return add(item); - } - - @Override - public void show(Component invoker, int x, int y) { - JTextComponent textComponent = (JTextComponent) invoker; - boolean editable = textComponent.isEditable() && textComponent.isEnabled(); - cutItem.setVisible(editable); - pasteItem.setVisible(editable); - deleteItem.setVisible(editable); - super.show(invoker, x, y); - } - - @Override - public void actionPerformed(ActionEvent e) { - JTextComponent textComponent = (JTextComponent) getInvoker(); - textComponent.requestFocus(); - - boolean haveSelection = - textComponent.getSelectionStart() != textComponent.getSelectionEnd(); - - if (e.getSource() == cutItem) { - if (!haveSelection) textComponent.selectAll(); - textComponent.cut(); - } else if (e.getSource() == copyItem) { - if (!haveSelection) textComponent.selectAll(); - textComponent.copy(); - } else if (e.getSource() == pasteItem) { - textComponent.paste(); - } else if (e.getSource() == deleteItem) { - if (!haveSelection) textComponent.selectAll(); - textComponent.replaceSelection(""); - } else if (e.getSource() == selectAllItem) { - textComponent.selectAll(); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/skcraft/launcher/swing/WebpageLayoutManager.java b/src/main/java/com/skcraft/launcher/swing/WebpageLayoutManager.java deleted file mode 100644 index 74ee2de..0000000 --- a/src/main/java/com/skcraft/launcher/swing/WebpageLayoutManager.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import javax.swing.*; -import java.awt.*; - -public class WebpageLayoutManager implements LayoutManager { - - private static final int PROGRESS_WIDTH = 100; - - @Override - public void addLayoutComponent(String name, Component comp) { - } - - @Override - public void removeLayoutComponent(Component comp) { - throw new UnsupportedOperationException("Can't remove things!"); - } - - @Override - public Dimension preferredLayoutSize(Container parent) { - return new Dimension(0, 0); - } - - @Override - public Dimension minimumLayoutSize(Container parent) { - return new Dimension(0, 0); - } - - @Override - public void layoutContainer(Container parent) { - Insets insets = parent.getInsets(); - int maxWidth = parent.getWidth() - (insets.left + insets.right); - int maxHeight = parent.getHeight() - (insets.top + insets.bottom); - - int numComps = parent.getComponentCount(); - for (int i = 0 ; i < numComps ; i++) { - Component comp = parent.getComponent(i); - - if (comp instanceof JProgressBar) { - Dimension size = comp.getPreferredSize(); - comp.setLocation((parent.getWidth() - PROGRESS_WIDTH) / 2, - (int) (parent.getHeight() / 2.0 - size.height / 2.0)); - comp.setSize(PROGRESS_WIDTH, - (int) comp.getPreferredSize().height); - } else { - comp.setLocation(insets.left, insets.top); - comp.setSize(maxWidth, maxHeight); - } - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/swing/WebpagePanel.java b/src/main/java/com/skcraft/launcher/swing/WebpagePanel.java deleted file mode 100644 index 86d5231..0000000 --- a/src/main/java/com/skcraft/launcher/swing/WebpagePanel.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.swing; - -import com.skcraft.launcher.LauncherUtils; -import lombok.extern.java.Log; - -import javax.swing.*; -import javax.swing.border.CompoundBorder; -import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; -import javax.swing.text.html.HTMLDocument; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Enumeration; -import java.util.logging.Level; - -import static com.skcraft.launcher.LauncherUtils.checkInterrupted; - -@Log -public final class WebpagePanel extends JPanel { - - private final WebpagePanel self = this; - - private URL url; - private boolean activated; - private JEditorPane documentView; - private JProgressBar progressBar; - private Thread thread; - - public static WebpagePanel forURL(URL url, boolean lazy) { - return new WebpagePanel(url, lazy); - } - - public static WebpagePanel forHTML(String html) { - return new WebpagePanel(html); - } - - private WebpagePanel(URL url, boolean lazy) { - this.url = url; - - setLayout(new BorderLayout()); - - if (lazy) { - setPlaceholder(); - } else { - setDocument(); - fetchAndDisplay(url); - } - } - - private WebpagePanel(String text) { - this.url = null; - - setLayout(new BorderLayout()); - - setDocument(); - setDisplay(text, null); - } - - public WebpagePanel(boolean lazy) { - this.url = null; - - setLayout(new BorderLayout()); - - if (lazy) { - setPlaceholder(); - } else { - setDocument(); - } - } - - private void setDocument() { - activated = true; - - JLayeredPane panel = new JLayeredPane(); - panel.setLayout(new WebpageLayoutManager()); - - documentView = new JEditorPane(); - documentView.setBorder(null); - documentView.setEditable(false); - documentView.addHyperlinkListener(new HyperlinkListener() { - @Override - public void hyperlinkUpdate(HyperlinkEvent e) { - if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - if (e.getURL() != null) { - SwingHelper.openURL(e.getURL(), self); - } - } - } - }); - - JScrollPane scrollPane = new JScrollPane(documentView); - panel.add(scrollPane, new Integer(1)); - scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); - - progressBar = new JProgressBar(); - progressBar.setIndeterminate(true); - panel.add(progressBar, new Integer(2)); - - add(panel, BorderLayout.CENTER); - } - - private void setPlaceholder() { - activated = false; - - JLayeredPane panel = new JLayeredPane(); - panel.setBorder(new CompoundBorder( - BorderFactory.createEtchedBorder(), BorderFactory - .createEmptyBorder(4, 4, 4, 4))); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - - final JButton showButton = new JButton("Load page"); - showButton.setAlignmentX(Component.CENTER_ALIGNMENT); - showButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - showButton.setVisible(false); - setDocument(); - fetchAndDisplay(url); - } - }); - - // Center the button vertically. - panel.add(new Box.Filler( - new Dimension(0, 0), - new Dimension(0, 0), - new Dimension(1000, 1000))); - panel.add(showButton); - panel.add(new Box.Filler( - new Dimension(0, 0), - new Dimension(0, 0), - new Dimension(1000, 1000))); - - add(panel, BorderLayout.CENTER); - } - - /** - * Browse to a URL. - * - * @param url the URL - * @param onlyChanged true to only browse if the last URL was different - * @return true if only the URL was changed - */ - public boolean browse(URL url, boolean onlyChanged) { - if (onlyChanged && this.url != null && this.url.equals(url)) { - return false; - } - - this.url = url; - - if (activated) { - fetchAndDisplay(url); - } - - return true; - } - - /** - * Update the page. This has to be run in the Swing event thread. - * - * @param url the URL - */ - private synchronized void fetchAndDisplay(URL url) { - if (thread != null) { - thread.interrupt(); - } - - progressBar.setVisible(true); - - thread = new Thread(new FetchWebpage(url)); - thread.setDaemon(true); - thread.start(); - } - - private void setDisplay(String text, URL baseUrl) { - progressBar.setVisible(false); - documentView.setContentType("text/html"); - HTMLDocument document = (HTMLDocument) documentView.getDocument(); - - // Clear existing styles - Enumeration e = document.getStyleNames(); - while (e.hasMoreElements()) { - Object o = e.nextElement(); - document.removeStyle((String) o); - } - - document.setBase(baseUrl); - documentView.setText(text); - - documentView.setCaretPosition(0); - } - - private void setError(String text) { - progressBar.setVisible(false); - documentView.setContentType("text/plain"); - documentView.setText(text); - documentView.setCaretPosition(0); - } - - private class FetchWebpage implements Runnable { - private URL url; - - public FetchWebpage(URL url) { - this.url = url; - } - - @Override - public void run() { - HttpURLConnection conn = null; - - try { - conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setUseCaches(false); - conn.setDoInput(true); - conn.setDoOutput(false); - conn.setReadTimeout(5000); - - conn.connect(); - - checkInterrupted(); - - if (conn.getResponseCode() != 200) { - throw new IOException( - "Did not get expected 200 code, got " - + conn.getResponseCode()); - } - - BufferedReader reader = new BufferedReader( - new InputStreamReader(conn.getInputStream(), - "UTF-8")); - - StringBuilder s = new StringBuilder(); - char[] buf = new char[1024]; - int len = 0; - while ((len = reader.read(buf)) != -1) { - s.append(buf, 0, len); - } - String result = s.toString(); - - checkInterrupted(); - - setDisplay(result, LauncherUtils.concat(url, "")); - } catch (IOException e) { - if (Thread.interrupted()) { - return; - } - - log.log(Level.WARNING, "Failed to fetch page", e); - setError("Failed to fetch page: " + e.getMessage()); - } catch (InterruptedException e) { - } finally { - if (conn != null) - conn.disconnect(); - conn = null; - } - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/update/BaseUpdater.java b/src/main/java/com/skcraft/launcher/update/BaseUpdater.java deleted file mode 100644 index 9fa52a8..0000000 --- a/src/main/java/com/skcraft/launcher/update/BaseUpdater.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.update; - -import com.google.common.base.Strings; -import com.skcraft.launcher.AssetsRoot; -import com.skcraft.launcher.Instance; -import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.LauncherException; -import com.skcraft.launcher.dialog.FeatureSelectionDialog; -import com.skcraft.launcher.dialog.ProgressDialog; -import com.skcraft.launcher.install.*; -import com.skcraft.launcher.model.minecraft.Asset; -import com.skcraft.launcher.model.minecraft.AssetsIndex; -import com.skcraft.launcher.model.minecraft.Library; -import com.skcraft.launcher.model.minecraft.VersionManifest; -import com.skcraft.launcher.model.modpack.Feature; -import com.skcraft.launcher.model.modpack.Manifest; -import com.skcraft.launcher.model.modpack.ManifestEntry; -import com.skcraft.launcher.persistence.Persistence; -import com.skcraft.launcher.util.Environment; -import com.skcraft.launcher.util.HttpRequest; -import lombok.NonNull; -import lombok.extern.java.Log; - -import javax.swing.*; -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.*; -import java.util.logging.Level; - -import static com.skcraft.launcher.LauncherUtils.checkInterrupted; -import static com.skcraft.launcher.LauncherUtils.concat; -import static com.skcraft.launcher.util.SharedLocale._; - -/** - * The base implementation of the various routines involved in downloading - * and updating Minecraft (including the launcher's modpacks), such as asset - * downloading, .jar downloading, and so on. - *

- * Updating actually starts in {@link com.skcraft.launcher.update.Updater}, - * which is the update worker. This class exists to allow updaters that don't - * use the launcher's default modpack format to reuse these update - * routines. (It also makes the size of the Updater class smaller.) - */ -@Log -public abstract class BaseUpdater { - - private static final long JAR_SIZE_ESTIMATE = 5 * 1024 * 1024; - private static final long LIBRARY_SIZE_ESTIMATE = 3 * 1024 * 1024; - - private final Launcher launcher; - private final Environment environment = Environment.getInstance(); - private final List executeOnCompletion = new ArrayList(); - - protected BaseUpdater(@NonNull Launcher launcher) { - this.launcher = launcher; - } - - protected void complete() { - for (Runnable runnable : executeOnCompletion) { - runnable.run(); - } - } - - protected Manifest installPackage(@NonNull Installer installer, @NonNull Instance instance) throws Exception { - final File contentDir = instance.getContentDir(); - final File logPath = new File(instance.getDir(), "install_log.json"); - final File cachePath = new File(instance.getDir(), "update_cache.json"); - final File featuresPath = new File(instance.getDir(), "features.json"); - - final InstallLog previousLog = Persistence.read(logPath, InstallLog.class); - final InstallLog currentLog = new InstallLog(); - currentLog.setBaseDir(contentDir); - final UpdateCache updateCache = Persistence.read(cachePath, UpdateCache.class); - final FeatureCache featuresCache = Persistence.read(featuresPath, FeatureCache.class); - - Manifest manifest = HttpRequest - .get(instance.getManifestURL()) - .execute() - .expectResponseCode(200) - .returnContent() - .saveContent(instance.getManifestPath()) - .asJson(Manifest.class); - - if (manifest.getMinimumVersion() > Launcher.PROTOCOL_VERSION) { - throw new LauncherException("Update required", _("errors.updateRequiredError")); - } - - if (manifest.getBaseUrl() == null) { - manifest.setBaseUrl(instance.getManifestURL()); - } - - final List features = manifest.getFeatures(); - if (!features.isEmpty()) { - for (Feature feature : features) { - Boolean last = featuresCache.getSelected().get(feature.getName()); - if (last != null) { - feature.setSelected(last); - } - } - - Collections.sort(features); - - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - new FeatureSelectionDialog(ProgressDialog.getLastDialog(), features).setVisible(true); - } - }); - - for (Feature feature : features) { - featuresCache.getSelected().put(Strings.nullToEmpty(feature.getName()), feature.isSelected()); - } - } - - for (ManifestEntry entry : manifest.getTasks()) { - entry.install(installer, currentLog, updateCache, contentDir); - } - - executeOnCompletion.add(new Runnable() { - @Override - public void run() { - for (Map.Entry> entry : previousLog.getEntrySet()) { - for (String path : entry.getValue()) { - if (!currentLog.has(path)) { - new File(contentDir, path).delete(); - } - } - } - - writeDataFile(logPath, currentLog); - writeDataFile(cachePath, updateCache); - writeDataFile(featuresPath, featuresCache); - } - }); - - return manifest; - } - - protected void installJar(@NonNull Installer installer, - @NonNull File jarFile, - @NonNull URL url) throws InterruptedException { - // If the JAR does not exist, install it - if (!jarFile.exists()) { - List targets = new ArrayList(); - - File tempFile = installer.getDownloader().download(url, "", JAR_SIZE_ESTIMATE, jarFile.getName()); - installer.queue(new FileMover(tempFile, jarFile)); - log.info("Installing " + jarFile.getName() + " from " + url); - } - } - - protected void installAssets(@NonNull Installer installer, - @NonNull VersionManifest versionManifest, - @NonNull URL indexUrl, - @NonNull List sources) throws IOException, InterruptedException { - AssetsRoot assetsRoot = launcher.getAssets(); - - AssetsIndex index = HttpRequest - .get(indexUrl) - .execute() - .expectResponseCode(200) - .returnContent() - .saveContent(assetsRoot.getIndexPath(versionManifest)) - .asJson(AssetsIndex.class); - - // Keep track of duplicates - Set downloading = new HashSet(); - - for (Map.Entry entry : index.getObjects().entrySet()) { - checkInterrupted(); - - String hash = entry.getValue().getHash(); - String path = String.format("%s/%s", hash.subSequence(0, 2), hash); - File targetFile = assetsRoot.getObjectPath(entry.getValue()); - - if (!targetFile.exists() && !downloading.contains(path)) { - List urls = new ArrayList(); - for (URL sourceUrl : sources) { - try { - urls.add(concat(sourceUrl, path)); - } catch (MalformedURLException e) { - log.log(Level.WARNING, "Bad source URL for library: " + sourceUrl); - } - } - - File tempFile = installer.getDownloader().download( - urls, "", entry.getValue().getSize(), entry.getKey()); - installer.queue(new FileMover(tempFile, targetFile)); - log.info("Fetching " + path + " from " + urls); - downloading.add(path); - } - } - } - - protected void installLibraries(@NonNull Installer installer, - @NonNull VersionManifest versionManifest, - @NonNull File librariesDir, - @NonNull List sources) throws InterruptedException { - - for (Library library : versionManifest.getLibraries()) { - if (library.matches(environment)) { - checkInterrupted(); - - String path = library.getPath(environment); - File targetFile = new File(librariesDir, path); - - if (!targetFile.exists()) { - List urls = new ArrayList(); - for (URL sourceUrl : sources) { - try { - urls.add(concat(sourceUrl, path)); - } catch (MalformedURLException e) { - log.log(Level.WARNING, "Bad source URL for library: " + sourceUrl); - } - } - - File tempFile = installer.getDownloader().download(urls, "", LIBRARY_SIZE_ESTIMATE, - library.getName() + ".jar"); - installer.queue(new FileMover( tempFile, targetFile)); - log.info("Fetching " + path + " from " + urls); - } - } - } - } - - private static void writeDataFile(File path, Object object) { - try { - Persistence.write(path, object); - } catch (IOException e) { - log.log(Level.WARNING, "Failed to write to " + path.getAbsolutePath() + - " for object " + object.getClass().getCanonicalName(), e); - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/update/HardResetter.java b/src/main/java/com/skcraft/launcher/update/HardResetter.java deleted file mode 100644 index cd9100a..0000000 --- a/src/main/java/com/skcraft/launcher/update/HardResetter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.update; - -import com.skcraft.concurrency.ProgressObservable; -import com.skcraft.launcher.Instance; -import com.skcraft.launcher.LauncherUtils; -import com.skcraft.launcher.persistence.Persistence; -import lombok.NonNull; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.concurrent.Callable; - -import static com.skcraft.launcher.util.SharedLocale._; - -public class HardResetter implements Callable, ProgressObservable { - - private final Instance instance; - private File currentDir; - - public HardResetter(@NonNull Instance instance) { - this.instance = instance; - } - - @Override - public double getProgress() { - return -1; - } - - @Override - public String getStatus() { - return _("instanceResetter.resetting", instance.getTitle()); - } - - @Override - public Instance call() throws Exception { - instance.setInstalled(false); - instance.setUpdatePending(true); - Persistence.commitAndForget(instance); - - new File(instance.getDir(), "update_cache.json").delete(); - - removeDir(new File(instance.getContentDir(), "config")); - removeDir(new File(instance.getContentDir(), "mods")); - - return instance; - } - - private void removeDir(File dir) throws IOException, InterruptedException { - try { - if (dir.isDirectory()) { - currentDir = dir; - LauncherUtils.interruptibleDelete(dir, new ArrayList()); - } - } finally { - currentDir = null; - } - } - - public String toString() { - File dir = currentDir; - if (dir != null) { - return "Removing " + dir.getAbsolutePath(); - } else { - return "Working..."; - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/update/Remover.java b/src/main/java/com/skcraft/launcher/update/Remover.java deleted file mode 100644 index 12f406f..0000000 --- a/src/main/java/com/skcraft/launcher/update/Remover.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.update; - -import com.skcraft.concurrency.ProgressObservable; -import com.skcraft.launcher.Instance; -import com.skcraft.launcher.LauncherException; -import com.skcraft.launcher.LauncherUtils; -import com.skcraft.launcher.persistence.Persistence; -import lombok.NonNull; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; - -import static com.skcraft.launcher.LauncherUtils.checkInterrupted; -import static com.skcraft.launcher.util.SharedLocale._; - -public class Remover implements Callable, ProgressObservable { - - private final Instance instance; - - public Remover(@NonNull Instance instance) { - this.instance = instance; - } - - @Override - public double getProgress() { - return -1; - } - - @Override - public String getStatus() { - return _("instanceDeleter.deleting", instance.getDir()); - } - - @Override - public Instance call() throws Exception { - instance.setInstalled(false); - instance.setUpdatePending(true); - Persistence.commitAndForget(instance); - - checkInterrupted(); - - Thread.sleep(2000); - - List failures = new ArrayList(); - - try { - LauncherUtils.interruptibleDelete(instance.getDir(), failures); - } catch (IOException e) { - Thread.sleep(1000); - LauncherUtils.interruptibleDelete(instance.getDir(), failures); - } - - if (failures.size() > 0) { - throw new LauncherException(failures.size() + " failed to delete", - _("instanceDeleter.failures", failures.size())); - } - - return instance; - } - -} diff --git a/src/main/java/com/skcraft/launcher/update/Updater.java b/src/main/java/com/skcraft/launcher/update/Updater.java deleted file mode 100644 index 9cc0fdd..0000000 --- a/src/main/java/com/skcraft/launcher/update/Updater.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.update; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.skcraft.concurrency.DefaultProgress; -import com.skcraft.concurrency.ProgressFilter; -import com.skcraft.concurrency.ProgressObservable; -import com.skcraft.launcher.Instance; -import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.LauncherException; -import com.skcraft.launcher.install.Installer; -import com.skcraft.launcher.model.minecraft.VersionManifest; -import com.skcraft.launcher.model.modpack.Manifest; -import com.skcraft.launcher.persistence.Persistence; -import com.skcraft.launcher.util.HttpRequest; -import lombok.Getter; -import lombok.NonNull; -import lombok.Setter; -import lombok.extern.java.Log; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; - -import static com.skcraft.launcher.util.HttpRequest.url; -import static com.skcraft.launcher.util.SharedLocale._; - -@Log -public class Updater extends BaseUpdater implements Callable, ProgressObservable { - - private final ObjectMapper mapper = new ObjectMapper(); - private final Installer installer; - private final Launcher launcher; - private final Instance instance; - - @Getter @Setter - private boolean online; - - private List librarySources = new ArrayList(); - private List assetsSources = new ArrayList(); - - private ProgressObservable progress = new DefaultProgress(-1, _("instanceUpdater.preparingUpdate")); - - public Updater(@NonNull Launcher launcher, @NonNull Instance instance) { - super(launcher); - - this.installer = new Installer(launcher.getInstallerDir()); - this.launcher = launcher; - this.instance = instance; - - librarySources.add(launcher.propUrl("librariesSource")); - assetsSources.add(launcher.propUrl("assetsSource")); - } - - @Override - public Instance call() throws Exception { - log.info("Checking for an update for '" + instance.getName() + "'..."); - - boolean updateRequired = !instance.isInstalled(); - boolean updateDesired = (instance.isUpdatePending() || updateRequired); - boolean updateCapable = (instance.getManifestURL() != null); - - if (!online && updateRequired) { - log.info("Can't update " + instance.getTitle() + " because offline"); - String message = _("updater.updateRequiredButOffline"); - throw new LauncherException("Update required but currently offline", message); - } - - if (updateDesired && !updateCapable) { - if (updateRequired) { - log.info("Update required for " + instance.getTitle() + " but there is no manifest"); - String message = _("updater.updateRequiredButNoManifest"); - throw new LauncherException("Update required but no manifest", message); - } else { - log.info("Can't update " + instance.getTitle() + ", but update is not required"); - return instance; // Can't update - } - } - - if (updateDesired) { - log.info("Updating " + instance.getTitle() + "..."); - update(instance); - } else { - log.info("No update found for " + instance.getTitle()); - } - - return instance; - } - - private VersionManifest readVersionManifest(Manifest manifest) throws IOException, InterruptedException { - // Check whether the package manifest contains an embedded version manifest, - // otherwise we'll have to download the one for the given Minecraft version - VersionManifest version = manifest.getVersionManifest(); - if (version != null) { - mapper.writeValue(instance.getVersionPath(), version); - return version; - } else { - URL url = url(String.format( - launcher.getProperties().getProperty("versionManifestUrl"), - manifest.getGameVersion())); - - return HttpRequest - .get(url) - .execute() - .expectResponseCode(200) - .returnContent() - .saveContent(instance.getVersionPath()) - .asJson(VersionManifest.class); - } - } - - /** - * Update the given instance. - * - * @param instance the instance - * @throws IOException thrown on I/O error - * @throws InterruptedException thrown on interruption - * @throws ExecutionException thrown on execution error - */ - protected void update(Instance instance) throws Exception { - // Mark this instance as local - instance.setLocal(true); - Persistence.commitAndForget(instance); - - // Read manifest - log.info("Reading package manifest..."); - progress = new DefaultProgress(-1, _("instanceUpdater.readingManifest")); - Manifest manifest = installPackage(installer, instance); - - // Update instance from manifest - manifest.update(instance); - - // Read version manifest - log.info("Reading version manifest..."); - progress = new DefaultProgress(-1, _("instanceUpdater.readingVersion")); - VersionManifest version = readVersionManifest(manifest); - - progress = new DefaultProgress(-1, _("instanceUpdater.buildingDownloadList")); - - // Install the .jar - File jarPath = launcher.getJarPath(version); - URL jarSource = launcher.propUrl("jarUrl", version.getId()); - log.info("JAR at " + jarPath.getAbsolutePath() + ", fetched from " + jarSource); - installJar(installer, jarPath, jarSource); - - // Download libraries - log.info("Enumerating libraries to download..."); - - URL url = manifest.getLibrariesUrl(); - if (url != null) { - log.info("Added library source: " + url); - librarySources.add(url); - } - - progress = new DefaultProgress(-1, _("instanceUpdater.collectingLibraries")); - installLibraries(installer, version, launcher.getLibrariesDir(), librarySources); - - // Download assets - log.info("Enumerating assets to download..."); - progress = new DefaultProgress(-1, _("instanceUpdater.collectingAssets")); - installAssets(installer, version, launcher.propUrl("assetsIndexUrl", version.getAssetsIndex()), assetsSources); - - log.info("Executing download phase..."); - progress = ProgressFilter.between(installer.getDownloader(), 0, 0.98); - installer.download(); - - log.info("Executing install phase..."); - progress = ProgressFilter.between(installer, 0.98, 1); - installer.execute(); - - log.info("Completing..."); - complete(); - - // Update the instance's information - log.info("Writing instance information..."); - instance.setVersion(manifest.getVersion()); - instance.setUpdatePending(false); - instance.setInstalled(true); - instance.setLocal(true); - Persistence.commitAndForget(instance); - - log.log(Level.INFO, instance.getName() + - " has been updated to version " + manifest.getVersion() + "."); - } - - @Override - public double getProgress() { - return progress.getProgress(); - } - - @Override - public String getStatus() { - return progress.getStatus(); - } - - -} diff --git a/src/main/java/com/skcraft/launcher/util/Environment.java b/src/main/java/com/skcraft/launcher/util/Environment.java deleted file mode 100644 index 828f827..0000000 --- a/src/main/java/com/skcraft/launcher/util/Environment.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.util; - -import lombok.Data; - -/** - * Represents information about the current environment. - */ -@Data -public class Environment { - - private final Platform platform; - private final String platformVersion; - private final String arch; - - /** - * Get an instance of the current environment. - * - * @return the current environment - */ - public static Environment getInstance() { - return new Environment(detectPlatform(), System.getProperty("os.version"), System.getProperty("os.arch")); - } - - public String getArchBits() { - return arch.contains("64") ? "64" : "32"; - } - - /** - * Detect the current platform. - * - * @return the current platform - */ - public static Platform detectPlatform() { - String osName = System.getProperty("os.name").toLowerCase(); - if (osName.contains("win")) - return Platform.WINDOWS; - if (osName.contains("mac")) - return Platform.MAC_OS_X; - if (osName.contains("solaris") || osName.contains("sunos")) - return Platform.SOLARIS; - if (osName.contains("linux")) - return Platform.LINUX; - if (osName.contains("unix")) - return Platform.LINUX; - - return Platform.UNKNOWN; - } - -} diff --git a/src/main/java/com/skcraft/launcher/util/HttpRequest.java b/src/main/java/com/skcraft/launcher/util/HttpRequest.java deleted file mode 100644 index cc23121..0000000 --- a/src/main/java/com/skcraft/launcher/util/HttpRequest.java +++ /dev/null @@ -1,522 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.util; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.skcraft.concurrency.ProgressObservable; -import lombok.Getter; -import lombok.extern.java.Log; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; -import java.io.*; -import java.net.*; -import java.util.*; - -import static com.skcraft.launcher.LauncherUtils.checkInterrupted; -import static org.apache.commons.io.IOUtils.closeQuietly; - -/** - * A simple fluent interface for performing HTTP requests that uses - * {@link java.net.HttpURLConnection} or {@link javax.net.ssl.HttpsURLConnection}. - */ -@Log -public class HttpRequest implements Closeable, ProgressObservable { - - private static final int READ_TIMEOUT = 1000 * 60 * 10; - private static final int READ_BUFFER_SIZE = 1024 * 8; - - private final ObjectMapper mapper = new ObjectMapper(); - private final Map headers = new HashMap(); - private final String method; - @Getter - private final URL url; - private String contentType; - private byte[] body; - private HttpURLConnection conn; - private InputStream inputStream; - - private long contentLength = -1; - private long readBytes = 0; - - /** - * Create a new HTTP request. - * - * @param method the method - * @param url the URL - */ - private HttpRequest(String method, URL url) { - this.method = method; - this.url = url; - } - - /** - * Set the content body to a JSON object with the content type of "application/json". - * - * @param object the object to serialize as JSON - * @return this object - * @throws java.io.IOException if the object can't be mapped - */ - public HttpRequest bodyJson(Object object) throws IOException { - contentType = "application/json"; - body = mapper.writeValueAsBytes(object); - return this; - } - - /** - * Submit form data. - * - * @param form the form - * @return this object - */ - public HttpRequest bodyForm(Form form) { - contentType = "application/x-www-form-urlencoded"; - body = form.toString().getBytes(); - return this; - } - - /** - * Add a header. - * - * @param key the header key - * @param value the header value - * @return this object - */ - public HttpRequest header(String key, String value) { - headers.put(key, value); - return this; - } - - /** - * Execute the request. - *

- * After execution, {@link #close()} should be called. - * - * @return this object - * @throws java.io.IOException on I/O error - */ - public HttpRequest execute() throws IOException { - boolean successful = false; - - try { - if (conn != null) { - throw new IllegalArgumentException("Connection already executed"); - } - - conn = (HttpURLConnection) reformat(url).openConnection(); - conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Java) SKMCLauncher"); - - if (body != null) { - conn.setRequestProperty("Content-Type", contentType); - conn.setRequestProperty("Content-Length", Integer.toString(body.length)); - conn.setDoInput(true); - } - - for (Map.Entry entry : headers.entrySet()) { - conn.setRequestProperty(entry.getKey(), entry.getValue()); - } - - conn.setRequestMethod(method); - conn.setUseCaches(false); - conn.setDoOutput(true); - conn.setReadTimeout(READ_TIMEOUT); - - conn.connect(); - - if (body != null) { - DataOutputStream out = new DataOutputStream(conn.getOutputStream()); - out.write(body); - out.flush(); - out.close(); - } - - inputStream = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? - conn.getInputStream() : conn.getErrorStream(); - - successful = true; - } finally { - if (!successful) { - close(); - } - } - - return this; - } - - /** - * Require that the response code is one of the given response codes. - * - * @param codes a list of codes - * @return this object - * @throws java.io.IOException if there is an I/O error or the response code is not expected - */ - public HttpRequest expectResponseCode(int... codes) throws IOException { - int responseCode = getResponseCode(); - - for (int code : codes) { - if (code == responseCode) { - return this; - } - } - - close(); - throw new IOException("Did not get expected response code, got " + responseCode + " for " + url); - } - - /** - * Get the response code. - * - * @return the response code - * @throws java.io.IOException on I/O error - */ - public int getResponseCode() throws IOException { - if (conn == null) { - throw new IllegalArgumentException("No connection has been made"); - } - - return conn.getResponseCode(); - } - - /** - * Get the input stream. - * - * @return the input stream - */ - public InputStream getInputStream() { - return inputStream; - } - - /** - * Buffer the returned response. - * - * @return the buffered response - * @throws java.io.IOException on I/O error - * @throws InterruptedException on interruption - */ - public BufferedResponse returnContent() throws IOException, InterruptedException { - if (inputStream == null) { - throw new IllegalArgumentException("No input stream available"); - } - - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - int b = 0; - while ((b = inputStream.read()) != -1) { - checkInterrupted(); - bos.write(b); - } - return new BufferedResponse(bos.toByteArray()); - } finally { - close(); - } - } - - /** - * Save the result to a file. - * - * @param file the file - * @return this object - * @throws java.io.IOException on I/O error - * @throws InterruptedException on interruption - */ - public HttpRequest saveContent(File file) throws IOException, InterruptedException { - FileOutputStream fos = null; - BufferedOutputStream bos = null; - - try { - fos = new FileOutputStream(file); - bos = new BufferedOutputStream(fos); - - saveContent(bos); - } finally { - closeQuietly(bos); - closeQuietly(fos); - } - - return this; - } - - /** - * Save the result to an output stream. - * - * @param out the output stream - * @return this object - * @throws java.io.IOException on I/O error - * @throws InterruptedException on interruption - */ - public HttpRequest saveContent(OutputStream out) throws IOException, InterruptedException { - BufferedInputStream bis; - - try { - String field = conn.getHeaderField("Content-Length"); - if (field != null) { - long len = Long.parseLong(field); - if (len >= 0) { // Let's just not deal with really big numbers - contentLength = len; - } - } - } catch (NumberFormatException e) { - } - - try { - bis = new BufferedInputStream(inputStream); - - byte[] data = new byte[READ_BUFFER_SIZE]; - int len = 0; - while ((len = bis.read(data, 0, READ_BUFFER_SIZE)) >= 0) { - out.write(data, 0, len); - readBytes += len; - checkInterrupted(); - } - } finally { - close(); - } - - return this; - } - - @Override - public double getProgress() { - if (contentLength >= 0) { - return readBytes / (double) contentLength; - } else { - return -1; - } - } - - @Override - public String getStatus() { - return null; - } - - @Override - public void close() throws IOException { - if (conn != null) conn.disconnect(); - } - - /** - * Perform a GET request. - * - * @param url the URL - * @return a new request object - */ - public static HttpRequest get(URL url) { - return request("GET", url); - } - - /** - * Perform a POST request. - * - * @param url the URL - * @return a new request object - */ - public static HttpRequest post(URL url) { - return request("POST", url); - } - - /** - * Perform a request. - * - * @param method the method - * @param url the URL - * @return a new request object - */ - public static HttpRequest request(String method, URL url) { - return new HttpRequest(method, url); - } - - /** - * Create a new {@link java.net.URL} and throw a {@link RuntimeException} if the URL - * is not valid. - * - * @param url the url - * @return a URL object - * @throws RuntimeException if the URL is invalid - */ - public static URL url(String url) { - try { - return new URL(url); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - - /** - * URL may contain spaces and other nasties that will cause a failure. - * - * @param existing the existing URL to transform - * @return the new URL, or old one if there was a failure - */ - private static URL reformat(URL existing) { - try { - URL url = new URL(existing.toString()); - URI uri = new URI( - url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), - url.getPath(), url.getQuery(), url.getRef()); - url = uri.toURL(); - return url; - } catch (MalformedURLException e) { - return existing; - } catch (URISyntaxException e) { - return existing; - } - } - - /** - * Used with {@link #bodyForm(Form)}. - */ - public final static class Form { - public final List elements = new ArrayList(); - - private Form() { - } - - /** - * Add a key/value to the form. - * - * @param key the key - * @param value the value - * @return this object - */ - public Form add(String key, String value) { - try { - elements.add(URLEncoder.encode(key, "UTF-8") + - "=" + URLEncoder.encode(value, "UTF-8")); - return this; - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - boolean first = true; - for (String element : elements) { - if (first) { - first = false; - } else { - builder.append("&"); - } - builder.append(element); - } - return builder.toString(); - } - - /** - * Create a new form. - * - * @return a new form - */ - public static Form form() { - return new Form(); - } - } - - /** - * Used to buffer the response in memory. - */ - public class BufferedResponse { - private final byte[] data; - - private BufferedResponse(byte[] data) { - this.data = data; - } - - /** - * Return the result as bytes. - * - * @return the data - */ - public byte[] asBytes() { - return data; - } - - /** - * Return the result as a string. - * - * @param encoding the encoding - * @return the string - * @throws java.io.IOException on I/O error - */ - public String asString(String encoding) throws IOException { - return new String(data, encoding); - } - - /** - * Return the result as an instance of the given class that has been - * deserialized from a JSON payload. - * - * @return the object - * @throws java.io.IOException on I/O error - */ - public T asJson(Class cls) throws IOException { - return mapper.readValue(asString("UTF-8"), cls); - } - - /** - * Return the result as an instance of the given class that has been - * deserialized from a XML payload. - * - * @return the object - * @throws java.io.IOException on I/O error - */ - @SuppressWarnings("unchecked") - public T asXml(Class cls) throws IOException { - try { - JAXBContext context = JAXBContext.newInstance(cls); - Unmarshaller um = context.createUnmarshaller(); - return (T) um.unmarshal(new ByteArrayInputStream(data)); - } catch (JAXBException e) { - throw new IOException(e); - } - } - - /** - * Save the result to a file. - * - * @param file the file - * @return this object - * @throws java.io.IOException on I/O error - * @throws InterruptedException on interruption - */ - public BufferedResponse saveContent(File file) throws IOException, InterruptedException { - FileOutputStream fos = null; - BufferedOutputStream bos = null; - - file.getParentFile().mkdirs(); - - try { - fos = new FileOutputStream(file); - bos = new BufferedOutputStream(fos); - - saveContent(bos); - } finally { - closeQuietly(bos); - closeQuietly(fos); - } - - return this; - } - - /** - * Save the result to an output stream. - * - * @param out the output stream - * @return this object - * @throws java.io.IOException on I/O error - * @throws InterruptedException on interruption - */ - public BufferedResponse saveContent(OutputStream out) throws IOException, InterruptedException { - out.write(data); - - return this; - } - } - -} diff --git a/src/main/java/com/skcraft/launcher/util/LimitLinesDocumentListener.java b/src/main/java/com/skcraft/launcher/util/LimitLinesDocumentListener.java deleted file mode 100644 index 92da7b1..0000000 --- a/src/main/java/com/skcraft/launcher/util/LimitLinesDocumentListener.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.util; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.BadLocationException; -import javax.swing.text.Document; -import javax.swing.text.Element; - -/** - * From http://tips4java.wordpress.com/2008/10/15/limit-lines-in-document/ - * - * @author Rob Camick - */ -public class LimitLinesDocumentListener implements DocumentListener { - private int maximumLines; - private boolean isRemoveFromStart; - - /** - * Specify the number of lines to be stored in the Document. Extra lines - * will be removed from the start or end of the Document, depending on - * the boolean value specified. - * - * @param maximumLines number of lines - * @param isRemoveFromStart true to remove from the start - */ - public LimitLinesDocumentListener(int maximumLines, - boolean isRemoveFromStart) { - setLimitLines(maximumLines); - this.isRemoveFromStart = isRemoveFromStart; - } - - /** - * Set the maximum number of lines to be stored in the Document - * - * @param maximumLines number of lines - */ - public void setLimitLines(int maximumLines) { - if (maximumLines < 1) { - throw new IllegalArgumentException("Maximum lines must be greater than 0"); - } - - this.maximumLines = maximumLines; - } - - @Override - public void insertUpdate(final DocumentEvent e) { - // Changes to the Document can not be done within the listener - // so we need to add the processing to the end of the EDT - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - removeLines(e); - } - }); - } - - @Override - public void removeUpdate(DocumentEvent e) { - } - - @Override - public void changedUpdate(DocumentEvent e) { - } - - private void removeLines(DocumentEvent e) { - // The root Element of the Document will tell us the total number - // of line in the Document. - - Document document = e.getDocument(); - Element root = document.getDefaultRootElement(); - - while (root.getElementCount() > maximumLines) { - if (isRemoveFromStart) { - removeFromStart(document, root); - } else { - removeFromEnd(document, root); - } - } - } - - private void removeFromStart(Document document, Element root) { - Element line = root.getElement(0); - int end = line.getEndOffset(); - - try { - document.remove(0, end); - } catch (BadLocationException ble) { - System.out.println(ble); - } - } - - private void removeFromEnd(Document document, Element root) { - // We use start minus 1 to make sure we remove the newline - // character of the previous line - - Element line = root.getElement(root.getElementCount() - 1); - int start = line.getStartOffset(); - int end = line.getEndOffset(); - - try { - document.remove(start - 1, end - start); - } catch (BadLocationException ble) { - System.out.println(ble); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/skcraft/launcher/util/PastebinPoster.java b/src/main/java/com/skcraft/launcher/util/PastebinPoster.java deleted file mode 100644 index 53a9666..0000000 --- a/src/main/java/com/skcraft/launcher/util/PastebinPoster.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.util; - -import java.io.*; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; - -public class PastebinPoster { - private static final int CONNECT_TIMEOUT = 5000; - private static final int READ_TIMEOUT = 5000; - - public static void paste(String code, PasteCallback callback) { - PasteProcessor processor = new PasteProcessor(code, callback); - Thread thread = new Thread(processor); - thread.start(); - } - - public static interface PasteCallback { - public void handleSuccess(String url); - public void handleError(String err); - } - - private static class PasteProcessor implements Runnable { - private String code; - private PasteCallback callback; - - public PasteProcessor(String code, PasteCallback callback) { - this.code = code; - this.callback = callback; - } - - @Override - public void run() { - HttpURLConnection conn = null; - OutputStream out = null; - InputStream in = null; - - try { - URL url = new URL("http://pastebin.com/api/api_post.php"); - conn = (HttpURLConnection) url.openConnection(); - conn.setConnectTimeout(CONNECT_TIMEOUT); - conn.setReadTimeout(READ_TIMEOUT); - conn.setRequestMethod("POST"); - conn.addRequestProperty("Content-type", "application/x-www-form-urlencoded"); - conn.setInstanceFollowRedirects(false); - conn.setDoOutput(true); - out = conn.getOutputStream(); - - out.write(("api_option=paste" - + "&api_dev_key=" + URLEncoder.encode("4867eae74c6990dbdef07c543cf8f805", "utf-8") - + "&api_paste_code=" + URLEncoder.encode(code, "utf-8") - + "&api_paste_private=" + URLEncoder.encode("0", "utf-8") - + "&api_paste_name=" + URLEncoder.encode("", "utf-8") - + "&api_paste_expire_date=" + URLEncoder.encode("1D", "utf-8") - + "&api_paste_format=" + URLEncoder.encode("text", "utf-8") - + "&api_user_key=" + URLEncoder.encode("", "utf-8")).getBytes()); - out.flush(); - out.close(); - - if (conn.getResponseCode() == 200) { - in = conn.getInputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - String line; - StringBuilder response = new StringBuilder(); - while ((line = reader.readLine()) != null) { - response.append(line); - response.append("\r\n"); - } - reader.close(); - - String result = response.toString().trim(); - - if (result.matches("^https?://.*")) { - callback.handleSuccess(result.trim()); - } else { - String err = result.trim(); - if (err.length() > 100) { - err = err.substring(0, 100); - } - callback.handleError(err); - } - } else { - callback.handleError("An error occurred while uploading the text."); - } - } catch (IOException e) { - callback.handleError(e.getMessage()); - } finally { - if (conn != null) { - conn.disconnect(); - } - if (in != null) { - try { - in.close(); - } catch (IOException ignored) { - } - } - if (out != null) { - try { - out.close(); - } catch (IOException ignored) { - } - } - } - } - - } - -} diff --git a/src/main/java/com/skcraft/launcher/util/Platform.java b/src/main/java/com/skcraft/launcher/util/Platform.java deleted file mode 100644 index a338351..0000000 --- a/src/main/java/com/skcraft/launcher/util/Platform.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.util; - -import javax.xml.bind.annotation.XmlEnumValue; - -/** - * Indicates the platform. - */ -public enum Platform { - @XmlEnumValue("windows") WINDOWS, - @XmlEnumValue("mac_os_x") MAC_OS_X, - @XmlEnumValue("linux") LINUX, - @XmlEnumValue("solaris") SOLARIS, - @XmlEnumValue("unknown") UNKNOWN -} \ No newline at end of file diff --git a/src/main/java/com/skcraft/launcher/util/SharedLocale.java b/src/main/java/com/skcraft/launcher/util/SharedLocale.java deleted file mode 100644 index 93df77a..0000000 --- a/src/main/java/com/skcraft/launcher/util/SharedLocale.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.util; - -import lombok.NonNull; -import lombok.extern.java.Log; - -import java.text.MessageFormat; -import java.util.Locale; -import java.util.MissingResourceException; -import java.util.ResourceBundle; -import java.util.logging.Level; - -/** - * Handles loading a shared message {@link java.util.ResourceBundle}. - */ -@Log -public class SharedLocale { - - private static Locale locale = Locale.getDefault(); - private static ResourceBundle bundle; - - /** - * Get the current locale. - * - * @return the current locale - */ - public static Locale getLocale() { - return locale; - } - - /** - * Get the current resource bundle. - * - * @return the current resource bundle, or null if not available - */ - public static ResourceBundle getBundle() { - return bundle; - } - - /** - * Translate a string. - * - *

If the string is not available, then ${key} will be returned.

- * - * @param key the key - * @return the translated string - */ - public static String _(String key) { - if (bundle != null) { - try { - return bundle.getString(key); - } catch (MissingResourceException e) { - log.log(Level.WARNING, "Failed to find message", e); - } - } - - return "${" + key + "}"; - } - - /** - * Format a translated string. - * - *

If the string is not available, then ${key}:args will be returned.

- * - * @param key the key - * @param args arguments - * @return a translated string - */ - public static String _(String key, Object... args) { - if (bundle != null) { - try { - MessageFormat formatter = new MessageFormat(_(key)); - formatter.setLocale(getLocale()); - return formatter.format(args); - } catch (MissingResourceException e) { - log.log(Level.WARNING, "Failed to find message", e); - } - } - - return "${" + key + "}:" + args; - } - - /** - * Load a shared resource bundle. - * - * @param baseName the bundle name - * @param locale the locale - * @return true if loaded successfully - */ - public static boolean loadBundle(@NonNull String baseName, @NonNull Locale locale) { - try { - SharedLocale.locale = locale; - bundle = ResourceBundle.getBundle(baseName, locale, - SharedLocale.class.getClassLoader()); - return true; - } catch (MissingResourceException e) { - log.log(Level.SEVERE, "Failed to load resource bundle", e); - return false; - } - } -} diff --git a/src/main/java/com/skcraft/launcher/util/SimpleLogFormatter.java b/src/main/java/com/skcraft/launcher/util/SimpleLogFormatter.java deleted file mode 100644 index 4cd9cc4..0000000 --- a/src/main/java/com/skcraft/launcher/util/SimpleLogFormatter.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.util; - -import lombok.extern.java.Log; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.logging.*; - -@Log -public final class SimpleLogFormatter extends Formatter { - - private static final String LINE_SEPARATOR = System.getProperty("line.separator"); - - @Override - public String format(LogRecord record) { - StringBuilder sb = new StringBuilder(); - - sb.append("[") - .append(record.getLevel().getLocalizedName().toLowerCase()) - .append("] ") - .append(formatMessage(record)) - .append(LINE_SEPARATOR); - - if (record.getThrown() != null) { - try { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - record.getThrown().printStackTrace(pw); - pw.close(); - sb.append(sw.toString()); - } catch (Exception e) { - } - } - - return sb.toString(); - } - - public static void configureGlobalLogger() { - Logger globalLogger = Logger.getLogger(""); - - // Set formatter - for (Handler handler : globalLogger.getHandlers()) { - handler.setFormatter(new SimpleLogFormatter()); - } - - // Set level - String logLevel = System.getProperty( - SimpleLogFormatter.class.getCanonicalName() + ".logLevel", "INFO"); - try { - Level level = Level.parse(logLevel); - globalLogger.setLevel(level); - } catch (IllegalArgumentException e) { - log.log(Level.WARNING, "Invalid log level of " + logLevel, e); - } - } - -} \ No newline at end of file diff --git a/src/main/java/com/skcraft/launcher/util/SwingExecutor.java b/src/main/java/com/skcraft/launcher/util/SwingExecutor.java deleted file mode 100644 index d3e24ec..0000000 --- a/src/main/java/com/skcraft/launcher/util/SwingExecutor.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.util; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.*; - -public final class SwingExecutor extends AbstractExecutorService { - - public static final SwingExecutor INSTANCE = new SwingExecutor(); - - private SwingExecutor() { - } - - @Override - public void execute(Runnable runnable) { - SwingUtilities.invokeLater(runnable); - } - - @Override - protected RunnableFuture newTaskFor(final Callable callable) { - return new FutureTask(callable) { - @Override - public void run() { - try { - super.run(); - } catch (Throwable e) { - setException(e); - } - } - }; - } - - @Override - public void shutdown() { - } - - @Override - public List shutdownNow() { - return new ArrayList(); - } - - @Override - public boolean isShutdown() { - return false; - } - - @Override - public boolean isTerminated() { - return false; - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return false; - } -} \ No newline at end of file diff --git a/src/main/java/com/skcraft/launcher/util/WinRegistry.java b/src/main/java/com/skcraft/launcher/util/WinRegistry.java deleted file mode 100644 index 9e51983..0000000 --- a/src/main/java/com/skcraft/launcher/util/WinRegistry.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.util; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.prefs.Preferences; - -public class WinRegistry { - public static final int HKEY_CURRENT_USER = 0x80000001; - public static final int HKEY_LOCAL_MACHINE = 0x80000002; - public static final int REG_SUCCESS = 0; - public static final int REG_NOTFOUND = 2; - public static final int REG_ACCESSDENIED = 5; - - private static final int KEY_ALL_ACCESS = 0xf003f; - private static final int KEY_READ = 0x20019; - private static Preferences userRoot = Preferences.userRoot(); - private static Preferences systemRoot = Preferences.systemRoot(); - private static Class userClass = userRoot.getClass(); - private static Method regOpenKey = null; - private static Method regCloseKey = null; - private static Method regQueryValueEx = null; - private static Method regEnumValue = null; - private static Method regQueryInfoKey = null; - private static Method regEnumKeyEx = null; - private static Method regCreateKeyEx = null; - private static Method regSetValueEx = null; - private static Method regDeleteKey = null; - private static Method regDeleteValue = null; - - static { - try { - regOpenKey = userClass.getDeclaredMethod("WindowsRegOpenKey", - new Class[] { int.class, byte[].class, int.class }); - regOpenKey.setAccessible(true); - regCloseKey = userClass.getDeclaredMethod("WindowsRegCloseKey", - new Class[] { int.class }); - regCloseKey.setAccessible(true); - regQueryValueEx = userClass.getDeclaredMethod( - "WindowsRegQueryValueEx", new Class[] { int.class, - byte[].class }); - regQueryValueEx.setAccessible(true); - regEnumValue = userClass.getDeclaredMethod("WindowsRegEnumValue", - new Class[] { int.class, int.class, int.class }); - regEnumValue.setAccessible(true); - regQueryInfoKey = userClass.getDeclaredMethod( - "WindowsRegQueryInfoKey1", new Class[] { int.class }); - regQueryInfoKey.setAccessible(true); - regEnumKeyEx = userClass.getDeclaredMethod("WindowsRegEnumKeyEx", - new Class[] { int.class, int.class, int.class }); - regEnumKeyEx.setAccessible(true); - regCreateKeyEx = userClass.getDeclaredMethod( - "WindowsRegCreateKeyEx", new Class[] { int.class, - byte[].class }); - regCreateKeyEx.setAccessible(true); - regSetValueEx = userClass.getDeclaredMethod("WindowsRegSetValueEx", - new Class[] { int.class, byte[].class, byte[].class }); - regSetValueEx.setAccessible(true); - regDeleteValue = userClass.getDeclaredMethod( - "WindowsRegDeleteValue", new Class[] { int.class, - byte[].class }); - regDeleteValue.setAccessible(true); - regDeleteKey = userClass.getDeclaredMethod("WindowsRegDeleteKey", - new Class[] { int.class, byte[].class }); - regDeleteKey.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private WinRegistry() { - } - - /** - * Read a value from key and value name - * - * @param hkey - * HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE - * @param key - * @param valueName - * @return the value - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws java.lang.reflect.InvocationTargetException - */ - public static String readString(int hkey, String key, String valueName) - throws IllegalArgumentException, IllegalAccessException, - InvocationTargetException { - if (hkey == HKEY_LOCAL_MACHINE) { - return readString(systemRoot, hkey, key, valueName); - } else if (hkey == HKEY_CURRENT_USER) { - return readString(userRoot, hkey, key, valueName); - } else { - throw new IllegalArgumentException("hkey=" + hkey); - } - } - - /** - * Read value(s) and value name(s) form given key - * - * @param hkey - * HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE - * @param key - * @return the value name(s) plus the value(s) - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws java.lang.reflect.InvocationTargetException - */ - public static Map readStringValues(int hkey, String key) - throws IllegalArgumentException, IllegalAccessException, - InvocationTargetException { - if (hkey == HKEY_LOCAL_MACHINE) { - return readStringValues(systemRoot, hkey, key); - } else if (hkey == HKEY_CURRENT_USER) { - return readStringValues(userRoot, hkey, key); - } else { - throw new IllegalArgumentException("hkey=" + hkey); - } - } - - /** - * Read the value name(s) from a given key - * - * @param hkey - * HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE - * @param key - * @return the value name(s) - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws java.lang.reflect.InvocationTargetException - */ - public static List readStringSubKeys(int hkey, String key) - throws IllegalArgumentException, IllegalAccessException, - InvocationTargetException { - if (hkey == HKEY_LOCAL_MACHINE) { - return readStringSubKeys(systemRoot, hkey, key); - } else if (hkey == HKEY_CURRENT_USER) { - return readStringSubKeys(userRoot, hkey, key); - } else { - throw new IllegalArgumentException("hkey=" + hkey); - } - } - - /** - * Create a key - * - * @param hkey - * HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE - * @param key - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws java.lang.reflect.InvocationTargetException - */ - public static void createKey(int hkey, String key) - throws IllegalArgumentException, IllegalAccessException, - InvocationTargetException { - int[] ret; - if (hkey == HKEY_LOCAL_MACHINE) { - ret = createKey(systemRoot, hkey, key); - regCloseKey - .invoke(systemRoot, new Object[] { new Integer(ret[0]) }); - } else if (hkey == HKEY_CURRENT_USER) { - ret = createKey(userRoot, hkey, key); - regCloseKey.invoke(userRoot, new Object[] { new Integer(ret[0]) }); - } else { - throw new IllegalArgumentException("hkey=" + hkey); - } - if (ret[1] != REG_SUCCESS) { - throw new IllegalArgumentException("rc=" + ret[1] + " key=" + key); - } - } - - /** - * Write a value in a given key/value name - * - * @param hkey - * @param key - * @param valueName - * @param value - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws java.lang.reflect.InvocationTargetException - */ - public static void writeStringValue(int hkey, String key, String valueName, - String value) throws IllegalArgumentException, - IllegalAccessException, InvocationTargetException { - if (hkey == HKEY_LOCAL_MACHINE) { - writeStringValue(systemRoot, hkey, key, valueName, value); - } else if (hkey == HKEY_CURRENT_USER) { - writeStringValue(userRoot, hkey, key, valueName, value); - } else { - throw new IllegalArgumentException("hkey=" + hkey); - } - } - - /** - * Delete a given key - * - * @param hkey - * @param key - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws java.lang.reflect.InvocationTargetException - */ - public static void deleteKey(int hkey, String key) - throws IllegalArgumentException, IllegalAccessException, - InvocationTargetException { - int rc = -1; - if (hkey == HKEY_LOCAL_MACHINE) { - rc = deleteKey(systemRoot, hkey, key); - } else if (hkey == HKEY_CURRENT_USER) { - rc = deleteKey(userRoot, hkey, key); - } - if (rc != REG_SUCCESS) { - throw new IllegalArgumentException("rc=" + rc + " key=" + key); - } - } - - /** - * delete a value from a given key/value name - * - * @param hkey - * @param key - * @param value - * @throws IllegalArgumentException - * @throws IllegalAccessException - * @throws java.lang.reflect.InvocationTargetException - */ - public static void deleteValue(int hkey, String key, String value) - throws IllegalArgumentException, IllegalAccessException, - InvocationTargetException { - int rc = -1; - if (hkey == HKEY_LOCAL_MACHINE) { - rc = deleteValue(systemRoot, hkey, key, value); - } else if (hkey == HKEY_CURRENT_USER) { - rc = deleteValue(userRoot, hkey, key, value); - } - if (rc != REG_SUCCESS) { - throw new IllegalArgumentException("rc=" + rc + " key=" + key - + " value=" + value); - } - } - - // ===================== - - private static int deleteValue(Preferences root, int hkey, String key, - String value) throws IllegalArgumentException, - IllegalAccessException, InvocationTargetException { - int[] handles = (int[]) regOpenKey.invoke(root, new Object[] { - new Integer(hkey), toCstr(key), new Integer(KEY_ALL_ACCESS) }); - if (handles[1] != REG_SUCCESS) { - return handles[1]; // can be REG_NOTFOUND, REG_ACCESSDENIED - } - int rc = ((Integer) regDeleteValue.invoke(root, new Object[] { - new Integer(handles[0]), toCstr(value) })).intValue(); - regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) }); - return rc; - } - - private static int deleteKey(Preferences root, int hkey, String key) - throws IllegalArgumentException, IllegalAccessException, - InvocationTargetException { - int rc = ((Integer) regDeleteKey.invoke(root, new Object[] { - new Integer(hkey), toCstr(key) })).intValue(); - return rc; // can REG_NOTFOUND, REG_ACCESSDENIED, REG_SUCCESS - } - - private static String readString(Preferences root, int hkey, String key, - String value) throws IllegalArgumentException, - IllegalAccessException, InvocationTargetException { - int[] handles = (int[]) regOpenKey.invoke(root, new Object[] { - new Integer(hkey), toCstr(key), new Integer(KEY_READ) }); - if (handles[1] != REG_SUCCESS) { - return null; - } - byte[] valb = (byte[]) regQueryValueEx.invoke(root, new Object[] { - new Integer(handles[0]), toCstr(value) }); - regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) }); - return (valb != null ? new String(valb).trim() : null); - } - - private static Map readStringValues(Preferences root, - int hkey, String key) throws IllegalArgumentException, - IllegalAccessException, InvocationTargetException { - HashMap results = new HashMap(); - int[] handles = (int[]) regOpenKey.invoke(root, new Object[] { - new Integer(hkey), toCstr(key), new Integer(KEY_READ) }); - if (handles[1] != REG_SUCCESS) { - return null; - } - int[] info = (int[]) regQueryInfoKey.invoke(root, - new Object[] { new Integer(handles[0]) }); - - int count = info[0]; // count - int maxlen = info[3]; // value length max - for (int index = 0; index < count; index++) { - byte[] name = (byte[]) regEnumValue.invoke(root, new Object[] { - new Integer(handles[0]), new Integer(index), - new Integer(maxlen + 1) }); - String value = readString(hkey, key, new String(name)); - results.put(new String(name).trim(), value); - } - regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) }); - return results; - } - - private static List readStringSubKeys(Preferences root, int hkey, - String key) throws IllegalArgumentException, - IllegalAccessException, InvocationTargetException { - List results = new ArrayList(); - int[] handles = (int[]) regOpenKey.invoke(root, new Object[] { - new Integer(hkey), toCstr(key), new Integer(KEY_READ) }); - if (handles[1] != REG_SUCCESS) { - return null; - } - int[] info = (int[]) regQueryInfoKey.invoke(root, - new Object[] { new Integer(handles[0]) }); - - int count = info[0]; // Fix: info[2] was being used here with wrong - // results. Suggested by davenpcj, confirmed by - // Petrucio - int maxlen = info[3]; // value length max - for (int index = 0; index < count; index++) { - byte[] name = (byte[]) regEnumKeyEx.invoke(root, new Object[] { - new Integer(handles[0]), new Integer(index), - new Integer(maxlen + 1) }); - results.add(new String(name).trim()); - } - regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) }); - return results; - } - - private static int[] createKey(Preferences root, int hkey, String key) - throws IllegalArgumentException, IllegalAccessException, - InvocationTargetException { - return (int[]) regCreateKeyEx.invoke(root, new Object[] { - new Integer(hkey), toCstr(key) }); - } - - private static void writeStringValue(Preferences root, int hkey, - String key, String valueName, String value) - throws IllegalArgumentException, IllegalAccessException, - InvocationTargetException { - int[] handles = (int[]) regOpenKey.invoke(root, new Object[] { - new Integer(hkey), toCstr(key), new Integer(KEY_ALL_ACCESS) }); - - regSetValueEx.invoke(root, new Object[] { new Integer(handles[0]), - toCstr(valueName), toCstr(value) }); - regCloseKey.invoke(root, new Object[] { new Integer(handles[0]) }); - } - - // utility - private static byte[] toCstr(String str) { - byte[] result = new byte[str.length() + 1]; - - for (int i = 0; i < str.length(); i++) { - result[i] = (byte) str.charAt(i); - } - result[str.length()] = 0; - return result; - } -} \ No newline at end of file diff --git a/src/main/resources/com/skcraft/launcher/custom_instance_icon.png b/src/main/resources/com/skcraft/launcher/custom_instance_icon.png deleted file mode 100644 index 867849c..0000000 --- a/src/main/resources/com/skcraft/launcher/custom_instance_icon.png +++ /dev/null Binary files differ diff --git a/src/main/resources/com/skcraft/launcher/download_icon.png b/src/main/resources/com/skcraft/launcher/download_icon.png deleted file mode 100644 index 7a76bcb..0000000 --- a/src/main/resources/com/skcraft/launcher/download_icon.png +++ /dev/null Binary files differ diff --git a/src/main/resources/com/skcraft/launcher/icon.png b/src/main/resources/com/skcraft/launcher/icon.png deleted file mode 100644 index 8c74095..0000000 --- a/src/main/resources/com/skcraft/launcher/icon.png +++ /dev/null Binary files differ diff --git a/src/main/resources/com/skcraft/launcher/instance_icon.png b/src/main/resources/com/skcraft/launcher/instance_icon.png deleted file mode 100644 index 8c74095..0000000 --- a/src/main/resources/com/skcraft/launcher/instance_icon.png +++ /dev/null Binary files differ diff --git a/src/main/resources/com/skcraft/launcher/lang/Launcher.properties b/src/main/resources/com/skcraft/launcher/lang/Launcher.properties deleted file mode 100644 index 18996ee..0000000 --- a/src/main/resources/com/skcraft/launcher/lang/Launcher.properties +++ /dev/null @@ -1,182 +0,0 @@ -# -# SK's Minecraft Launcher -# Copyright (C) 2010-2014 Albert Pham and contributors -# Please see LICENSE.txt for license information. -# - -errorTitle=An error has occurred -confirmTitle=Confirm - -context.cut=Cut -context.copy=Copy -context.paste=Paste -context.delete=Delete -context.selectAll=Select all - -errors.openUrlError=Failed to open URL\: {0} -errors.openDirError=Unable to open ''{0}''. Maybe it doesn''t exist? -errors.reportErrorPreface=To report this error, please provide\:\n\n -errors.genericError=An error has occurred. -errors.updateRequiredError=Please download a new version of the launcher to continue updating. See skcraft.com for more information. -errors.selfUpdateCheckError=Checking for an update to the launcher has failed. - -button.cancel=Cancel -button.ok=OK - -options.title = Options -options.useProxyCheck = Use following proxy in Minecraft -options.jvmPath=JVM path\: -options.jvmArguments=JVM arguments\: -options.64BitJavaWarning=Make sure to have 64-bit Java installed if you are planning to set the memory limits higher. -options.minMemory=Minimum memory (MB)\: -options.maxMemory=Maximum memory (MB)\: -options.permGen=PermGen (MB)\: -options.javaTab=Java -options.windowWidth=Window width\: -options.windowHeight=Window height\: -options.minecraftTab=Minecraft -options.proxyHost=Proxy host\: -options.proxyPort=Proxy port\: -options.proxyUsername=Proxy username\: -options.proxyPassword=Proxy password\: -options.proxyTab=Proxy -options.gameKey=Game key\: -options.advancedTab=Advanced -options.launcherConsole=Launcher console - -instance.openFolder=View folder -instance.openSaves=View saves -instance.openResourcePacks=View resource packs -instance.openScreenshots=View screenshots -instance.copyAsPath=Copy as path -instance.forceUpdate=Force update -instance.hardForceUpdate=Hard force update... -instance.deleteFiles=Delete files... -instance.confirmDelete=Are you sure that you wish to delete ALL THE FILES (screenshots, worlds, configs) for ''{0}''? -instance.deletingTitle=Deleting instance... -instance.deletingStatus=Deleting ''{0}'' and files... -instance.confirmHardUpdate=A hard force update will delete the contents of config/ and mods/ and then require an update. Are you sure that you want to continue? -instance.resettingTitle=Resetting instance... -instance.resettingStatus=Resetting ''{0}''... - -launcher.launch=Launch... -launcher.checkForUpdates=Check for updates -launcher.options=Options... -launcher.updateLauncher=Update launcher... -launcher.downloadUpdates=Download modpack updates -launcher.title=SKCraft Launcher (v{0}) -launcher.refreshList=Refresh list -launcher.checkingTitle=Getting available modpacks... -launcher.checkingStatus=Getting available modpacks... Please wait. -launcher.selfUpdatingTitle=Updating launcher... -launcher.selfUpdatingStatus=Downloading launcher update... -launcher.selfUpdateComplete=Restart the launcher to use the new version. -launcher.selfUpdateCompleteTitle=Update complete -launcher.updatingTitle=Updating... -launcher.updatingStatus=Updating ''{0}''... Please wait. -launcher.noInstanceError=Please select a modpack to launch. -launcher.noInstanceTitle=No Modpack Selected -launcher.launchingTItle=Launching the game... -launcher.launchingStatus=Launching ''{0}''. Please wait. -launcher.modpackColumn=Modpack -launcher.notInstalledHint=(not installed) -launcher.requiresUpdateHint=(requires update) -launcher.updatePendingHint=(pending update) - -login.rememberId=Remember my account in the list -login.rememberPassword=Remember my password -login.login=Login... -login.recoverAccount=Forgot your login? -login.playOffline=Play offline -login.title=Minecraft Login -login.idEmail=ID/Email\: -login.password=Password\: -login.forgetUser=Forget selected user -login.forgetPassword=Forget password -login.forgetAllPasswords=Forget all passwords... -login.confirmForgetAllPasswords=Are you sure that you want to forget all saved passwords? -login.forgetAllPasswordsTitle=Forget passwords -login.noPasswordError=Please enter a password. -login.noPasswordTitle=Missing Password -login.loggingInTitle=Logging in... -login.loggingInStatus=Logging in to Mojang... -login.noLoginError=Please enter your account details. -login.noLoginTitle=Missing Account -login.minecraftNotOwnedError=Sorry, Minecraft is not owned on that account. - -console.title=Messages and Errors -console.launcherConsoleTitle=Launcher Messages -console.uploadLog=Upload Log -console.pasteUploading=Uploading {0} bytes...\n -console.pasteUploaded=Paste uploaded\: {0}\n -console.pasteFailed=Upload failed\: {0}\n -console.processEndCode=Process ended with code\: {0} -console.attachedToProcess=The game is running. Please wait. -console.forceClose=Force Close -console.trayTooltip=SKCraft Launcher -console.trayTitle=SKCraft Launcher -console.tray.showWindow=Show window -console.tray.forceClose=Force close... -console.closeWindow=Close Window -console.hideWindow=Hide Window -console.confirmKill=Are sure that you wish to close the game forcefully? You may lose data. -console.confirmKillTitle=Are you sure? - -downloader.downloadingItem=Downloading {0}... -downloader.downloadingList=Downloading {0} files... ({1} remaining, {2} failed) -downloader.jobProgress={1,number}%\t{0} -downloader.jobPending=...\t{0} -downloader.noDownloads=No pending downloads. -downloader.failedCount=({0} have failed) - -progress.details=Details... -progress.less=Less... -progress.viewLog=View log -progress.confirmCancel=Are you sure that you wish to cancel? -progress.confirmCancelTitle=Cancel -progress.defaultStatus=Working... -progress.percentTitle=({0}%) {1} - -installer.installing=Installing... -installer.executing=Executing tasks... ({0} remaining) -installer.copyingFile=Copying from {0} to {1} -installer.movingFile=Moving {0} to {1} - -updater.updating=Updating launcher... -updater.updateRequiredButOffline=An update is required but you need to be in online mode. -updater.updateRequiredButNoManifest=An update is required but update information for this instance is no longer available. - -instanceUpdater.preparingUpdate=Preparing to update... -instanceUpdater.readingManifest=Reading package manifest... -instanceUpdater.readingVersion=Reading version manifest... -instanceUpdater.buildingDownloadList=Collecting files to download... -instanceUpdater.collectingLibraries=Collecting libraries to download... -instanceUpdater.collectingAssets=Collecting assets to download... - -instanceDeleter.deleting=Deleting {0}... -instanceDeleter.failures={0} file(s) could not be deleted. - -instanceResetter.resetting=Resetting {0}... -instanceLoader.loadingLocal=Loading local instances from disk... -instanceLoader.checkingRemote=Checking for new modpacks... - -runner.preparing=Preparing for launch... -runner.collectingArgs=Collecting process arguments... -runner.startingJava=Starting java... -runner.updateRequired=This instance must be updated before it can be run. -runner.missingLibrary={0} needs to be relaunched and updated because the library ''{1}'' is missing. -runner.missingAssetsIndex={0} needs to be relaunched and updated because its asset index is missing. -runner.corruptAssetsIndex={0} needs to be relaunched and updated because its asset index is corrupt. - -assets.expanding1=Expanding {0} asset... ({1} remaining) -assets.expandingN=Expanding {0} assets... ({1} remaining) -assets.missingIndex=You need to update this instance because its index file at ''{0}'' is missing. -assets.missingObject=You need to update this instance because the file at ''{0}'' is missing. - -features.nameColumn=Feature -features.title=Select Features -features.install=OK -features.selectForInfo=Select a feature to see more information. -features.intro=Please select the optional features to install. -features.starred=(recommended) -features.avoid=(not recommended) diff --git a/src/main/resources/com/skcraft/launcher/launcher.properties b/src/main/resources/com/skcraft/launcher/launcher.properties deleted file mode 100644 index 7dbeb20..0000000 --- a/src/main/resources/com/skcraft/launcher/launcher.properties +++ /dev/null @@ -1,21 +0,0 @@ -# -# SK's Minecraft Launcher -# Copyright (C) 2010-2014 Albert Pham and contributors -# Please see LICENSE.txt for license information. -# - -version=${project.version} -agentName=Minecraft -offlinePlayerName=Player - -versionManifestUrl=https://s3.amazonaws.com/Minecraft.Download/versions/%1$s/%1$s.json -librariesSource=https://libraries.minecraft.net/ -jarUrl=http://s3.amazonaws.com/Minecraft.Download/versions/%1$s/%1$s.jar -assetsIndexUrl=https://s3.amazonaws.com/Minecraft.Download/indexes/%s.json -assetsSource=http://resources.download.minecraft.net/ -yggdrasilAuthUrl=https://authserver.mojang.com/authenticate -resetPasswordUrl=https://minecraft.net/resetpassword - -newsUrl=http://update.skcraft.com/template/news.html?version=%s -packageListUrl=http://update.skcraft.com/template/packages.json?key=%s -selfUpdateUrl=http://update.skcraft.com/template/launcher/latest.json diff --git a/src/main/resources/com/skcraft/launcher/maven_repos.json b/src/main/resources/com/skcraft/launcher/maven_repos.json deleted file mode 100644 index eecbdb4..0000000 --- a/src/main/resources/com/skcraft/launcher/maven_repos.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - "https://libraries.minecraft.net/", - "https://central.maven.org/maven2/", - "http://maven.apache.org/" -] \ No newline at end of file diff --git a/src/main/resources/com/skcraft/launcher/tray_closed.png b/src/main/resources/com/skcraft/launcher/tray_closed.png deleted file mode 100644 index 5e70e5b..0000000 --- a/src/main/resources/com/skcraft/launcher/tray_closed.png +++ /dev/null Binary files differ diff --git a/src/main/resources/com/skcraft/launcher/tray_ok.png b/src/main/resources/com/skcraft/launcher/tray_ok.png deleted file mode 100644 index 867849c..0000000 --- a/src/main/resources/com/skcraft/launcher/tray_ok.png +++ /dev/null Binary files differ