mirror of
https://github.com/SKCraft/Launcher.git
synced 2024-11-24 12:16:28 +01:00
Added support for user files and features.
This commit is contained in:
parent
eb34fa7560
commit
fc964d0d66
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
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;
|
||||
private List<FeaturePattern> features;
|
||||
private FnPatternList userFiles;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -13,6 +13,7 @@ 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;
|
||||
@ -25,6 +26,7 @@ import java.io.IOException;
|
||||
public class ClientFileCollector extends DirectoryWalker {
|
||||
|
||||
private final Manifest manifest;
|
||||
private final PropertiesApplicator applicator;
|
||||
private final File destDir;
|
||||
private HashFunction hf = Hashing.sha1();
|
||||
|
||||
@ -32,17 +34,45 @@ public class ClientFileCollector extends DirectoryWalker {
|
||||
* Create a new collector.
|
||||
*
|
||||
* @param manifest the manifest
|
||||
* @param destDir the destination directory to copy the hashed objects
|
||||
* @param applicator applies properties to manifest entries
|
||||
* @param destDir the destination directory to copy the hashed objects
|
||||
*/
|
||||
public ClientFileCollector(@NonNull Manifest manifest, @NonNull File destDir) {
|
||||
public ClientFileCollector(@NonNull Manifest manifest, @NonNull PropertiesApplicator applicator,
|
||||
@NonNull File destDir) {
|
||||
this.manifest = manifest;
|
||||
this.applicator = applicator;
|
||||
this.destDir = destDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DirectoryBehavior getBehavior(@NonNull String name) {
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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(FilenameUtils.separatorsToUnix(FilenameUtils.normalize(relPath)));
|
||||
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.equals("_OPTIONAL")) {
|
||||
return DirectoryBehavior.SKIP;
|
||||
return DirectoryBehavior.IGNORE;
|
||||
} else if (name.equals("_SERVER")) {
|
||||
return DirectoryBehavior.SKIP;
|
||||
} else if (name.equals("_CLIENT")) {
|
||||
@ -52,20 +82,4 @@ public class ClientFileCollector extends DirectoryWalker {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFile(File file, String relPath) throws IOException {
|
||||
FileInstall task = 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);
|
||||
task.setHash(hash);
|
||||
task.setLocation(hashedPath);
|
||||
task.setTo(relPath);
|
||||
task.setSize(file.length());
|
||||
destPath.getParentFile().mkdirs();
|
||||
ClientFileCollector.log.info(String.format("Adding %s from %s...", relPath, file.getAbsolutePath()));
|
||||
Files.copy(file, destPath);
|
||||
manifest.getTasks().add(task);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ public abstract class DirectoryWalker {
|
||||
* @param name the directory name
|
||||
* @return the behavor
|
||||
*/
|
||||
public DirectoryBehavior getBehavior(String name) {
|
||||
protected DirectoryBehavior getBehavior(String name) {
|
||||
return DirectoryBehavior.CONTINUE;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.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);
|
||||
}
|
||||
}
|
17
src/main/java/com/skcraft/launcher/builder/FileInfo.java
Normal file
17
src/main/java/com/skcraft/launcher/builder/FileInfo.java
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.builder;
|
||||
|
||||
import com.skcraft.launcher.model.modpack.Feature;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FileInfo {
|
||||
|
||||
private Feature feature;
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.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<FnMatch.Flag> 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<FeaturePattern> patterns = new ArrayList<FeaturePattern>();
|
||||
|
||||
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<String> patterns = new ArrayList<String>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
246
src/main/java/com/skcraft/launcher/builder/FnMatch.java
Normal file
246
src/main/java/com/skcraft/launcher/builder/FnMatch.java
Normal file
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> 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 /<tail> 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<Flag> 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<Flag> 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<Flag> 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<Flag> 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.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<FnMatch.Flag> DEFAULT_FLAGS = EnumSet.of(
|
||||
FnMatch.Flag.CASEFOLD, FnMatch.Flag.PERIOD);
|
||||
|
||||
private List<String> include;
|
||||
private List<String> exclude;
|
||||
@Getter @Setter @JsonIgnore
|
||||
private EnumSet<FnMatch.Flag> 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<String> patterns) {
|
||||
for (String pattern : patterns) {
|
||||
if (FnMatch.fnmatch(pattern, path, flags)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -20,6 +20,9 @@ import lombok.extern.java.Log;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.emptyToNull;
|
||||
|
||||
/**
|
||||
* Builds packages for the launcher.
|
||||
*/
|
||||
@ -29,6 +32,7 @@ public class PackageBuilder {
|
||||
private final ObjectMapper mapper;
|
||||
private ObjectWriter writer;
|
||||
private final Manifest manifest;
|
||||
private final PropertiesApplicator applicator;
|
||||
@Getter
|
||||
private boolean prettyPrint = false;
|
||||
|
||||
@ -41,14 +45,10 @@ public class PackageBuilder {
|
||||
public PackageBuilder(@NonNull ObjectMapper mapper, @NonNull Manifest manifest) {
|
||||
this.mapper = mapper;
|
||||
this.manifest = manifest;
|
||||
this.applicator = new PropertiesApplicator(manifest);
|
||||
setPrettyPrint(false); // Set writer
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether pretty printing should be used.
|
||||
*
|
||||
* @param prettyPrint true to pretty print
|
||||
*/
|
||||
public void setPrettyPrint(boolean prettyPrint) {
|
||||
if (prettyPrint) {
|
||||
writer = mapper.writerWithDefaultPrettyPrinter();
|
||||
@ -58,41 +58,68 @@ public class PackageBuilder {
|
||||
this.prettyPrint = prettyPrint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the files in the given directory.
|
||||
*
|
||||
* @param dir the directory
|
||||
* @param destDir the directory to copy the files to
|
||||
* @throws IOException thrown on I/O error
|
||||
*/
|
||||
private void addFiles(File dir, File destDir) throws IOException {
|
||||
ClientFileCollector collector = new ClientFileCollector(this.manifest, destDir);
|
||||
public void scan(File dir) throws IOException {
|
||||
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 {
|
||||
ClientFileCollector collector = new ClientFileCollector(this.manifest, applicator, destDir);
|
||||
collector.walk(dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the manifest to a file.
|
||||
*
|
||||
* @param path the path
|
||||
* @throws IOException thrown on I/O error
|
||||
*/
|
||||
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);
|
||||
manifest.updateName(config.getName());
|
||||
manifest.updateTitle(config.getTitle());
|
||||
manifest.updateGameVersion(config.getGameVersion());
|
||||
config.registerProperties(applicator);
|
||||
}
|
||||
}
|
||||
|
||||
public void readVersionManifest(File path) throws IOException {
|
||||
if (path != null) {
|
||||
VersionManifest versionManifest = read(path, VersionManifest.class);
|
||||
manifest.setVersionManifest(versionManifest);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeManifest(@NonNull File path) throws IOException {
|
||||
manifest.setFeatures(applicator.getFeaturesInUse());
|
||||
validateManifest();
|
||||
path.getParentFile().mkdirs();
|
||||
writer.writeValue(path, manifest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse arguments for the builder.
|
||||
*
|
||||
* @param args arguments
|
||||
* @return options
|
||||
*/
|
||||
private static PackageOptions parseArgs(String[] args) {
|
||||
PackageOptions options = new PackageOptions();
|
||||
new JCommander(options, args);
|
||||
return options;
|
||||
}
|
||||
|
||||
private <V> V read(File path, Class<V> 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.
|
||||
*
|
||||
@ -100,32 +127,33 @@ public class PackageBuilder {
|
||||
* @throws IOException thrown on I/O error
|
||||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
// May throw error here
|
||||
PackageOptions options = parseArgs(args);
|
||||
|
||||
// Initialize
|
||||
SimpleLogFormatter.configureGlobalLogger();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT);
|
||||
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.setName(options.getName());
|
||||
manifest.setTitle(options.getTitle());
|
||||
manifest.setVersion(options.getVersion());
|
||||
manifest.setGameVersion(options.getGameVersion());
|
||||
manifest.setLibrariesLocation(options.getLibrariesLocation());
|
||||
manifest.setObjectsLocation(options.getObjectsLocation());
|
||||
|
||||
File path = options.getVersionManifestPath();
|
||||
if (path != null) {
|
||||
manifest.setVersionManifest(mapper.readValue(path, VersionManifest.class));
|
||||
}
|
||||
|
||||
PackageBuilder builder = new PackageBuilder(mapper, manifest);
|
||||
builder.setPrettyPrint(options.isPrettyPrinting());
|
||||
|
||||
log.info("Adding files...");
|
||||
// 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.writeManifest(options.getManifestPath());
|
||||
|
||||
log.info("Wrote manifest to " + options.getManifestPath().getAbsolutePath());
|
||||
log.info("Done.");
|
||||
}
|
||||
|
@ -14,36 +14,37 @@ import java.io.File;
|
||||
@Data
|
||||
public class PackageOptions {
|
||||
|
||||
@Parameter(names = "--name", required = true)
|
||||
private String name;
|
||||
|
||||
@Parameter(names = "--title", required = true)
|
||||
private String title;
|
||||
|
||||
@Parameter(names = "--version", required = true)
|
||||
private String version;
|
||||
|
||||
@Parameter(names = "--mc-version", required = true)
|
||||
private String gameVersion;
|
||||
|
||||
@Parameter(names = "--manifest-path", required = true)
|
||||
private File manifestPath;
|
||||
|
||||
@Parameter(names = "--objects-dest", required = true)
|
||||
private File objectsDir;
|
||||
|
||||
@Parameter(names = "--files", required = true)
|
||||
private File filesDir;
|
||||
|
||||
// Configuration
|
||||
@Parameter(names = "--config")
|
||||
private File configPath;
|
||||
@Parameter(names = "--version-file")
|
||||
private File versionManifestPath;
|
||||
|
||||
@Parameter(names = "--libs-url")
|
||||
private String librariesLocation;
|
||||
|
||||
@Parameter(names = "--objects-url")
|
||||
private String objectsLocation;
|
||||
|
||||
// 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;
|
||||
|
||||
// Paths
|
||||
@Parameter(names = "--files", required = true)
|
||||
private File filesDir;
|
||||
@Parameter(names = "--manifest-dest", required = true)
|
||||
private File manifestPath;
|
||||
@Parameter(names = "--objects-dest", required = true)
|
||||
private File objectsDir;
|
||||
|
||||
// Misc
|
||||
@Parameter(names = "--pretty-print")
|
||||
private boolean prettyPrinting;
|
||||
|
||||
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.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<Feature> used = new HashSet<Feature>();
|
||||
private final List<FeaturePattern> features = new ArrayList<FeaturePattern>();
|
||||
@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<Feature> found = new ArrayList<Feature>();
|
||||
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<Feature> getFeaturesInUse() {
|
||||
return new ArrayList<Feature>(used);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.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<Feature> 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<Feature> 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);
|
||||
|
||||
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -21,6 +21,7 @@ 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;
|
||||
|
||||
@ -29,6 +30,8 @@ import static com.skcraft.launcher.util.SharedLocale._;
|
||||
@Log
|
||||
public class ProgressDialog extends JDialog {
|
||||
|
||||
private static WeakReference<ProgressDialog> lastDialogRef;
|
||||
|
||||
private final String defaultTitle;
|
||||
private final String defaultMessage;
|
||||
private final JLabel label = new JLabel();
|
||||
@ -159,6 +162,8 @@ public class ProgressDialog extends JDialog {
|
||||
}
|
||||
};
|
||||
|
||||
lastDialogRef = new WeakReference<ProgressDialog>(dialog);
|
||||
|
||||
final Timer timer = new Timer();
|
||||
timer.scheduleAtFixedRate(new UpdateProgress(dialog, future), 400, 400);
|
||||
|
||||
@ -179,6 +184,18 @@ public class ProgressDialog extends JDialog {
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
public static ProgressDialog getLastDialog() {
|
||||
WeakReference<ProgressDialog> ref = lastDialogRef;
|
||||
if (ref != null) {
|
||||
ProgressDialog dialog = ref.get();
|
||||
if (!dialog.isVisible()) {
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class UpdateProgress extends TimerTask {
|
||||
private final ProgressDialog dialog;
|
||||
private final ProgressObservable observable;
|
||||
|
19
src/main/java/com/skcraft/launcher/install/FeatureCache.java
Normal file
19
src/main/java/com/skcraft/launcher/install/FeatureCache.java
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.install;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class FeatureCache {
|
||||
|
||||
private Map<String, Boolean> selected = new HashMap<String, Boolean>();
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.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();
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.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<Feature> {
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ public class FileInstall extends ManifestEntry {
|
||||
private String location;
|
||||
private String to;
|
||||
private long size;
|
||||
private boolean userFile;
|
||||
|
||||
@JsonIgnore
|
||||
public String getImpliedVersion() {
|
||||
@ -46,12 +47,17 @@ public class FileInstall extends ManifestEntry {
|
||||
@Override
|
||||
public void install(@NonNull Installer installer, @NonNull InstallLog log,
|
||||
@NonNull UpdateCache cache, @NonNull File contentDir) throws MalformedURLException {
|
||||
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 (cache.mark(FilenameUtils.normalize(targetPath), fileVersion)) {
|
||||
if (!(isUserFile() && targetFile.exists()) &&
|
||||
(!targetFile.exists() || cache.mark(FilenameUtils.normalize(targetPath), fileVersion))) {
|
||||
long size = this.size;
|
||||
if (size <= 0) {
|
||||
size = 10 * 1024;
|
||||
|
@ -30,6 +30,7 @@ public class Manifest extends BaseManifest {
|
||||
private String librariesLocation;
|
||||
private String objectsLocation;
|
||||
private String gameVersion;
|
||||
private List<Feature> features = new ArrayList<Feature>();
|
||||
@JsonManagedReference("manifest")
|
||||
private List<ManifestEntry> tasks = new ArrayList<ManifestEntry>();
|
||||
@Getter @Setter @JsonIgnore
|
||||
@ -62,4 +63,21 @@ public class Manifest extends BaseManifest {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
package com.skcraft.launcher.model.modpack;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonBackReference;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.skcraft.launcher.install.InstallLog;
|
||||
@ -32,6 +31,7 @@ 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;
|
||||
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.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<Feature> features = new ArrayList<Feature>();
|
||||
|
||||
public RequireAll() {
|
||||
}
|
||||
|
||||
public RequireAll(List<Feature> 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.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<Feature> features = new ArrayList<Feature>();
|
||||
|
||||
public RequireAny() {
|
||||
}
|
||||
|
||||
public RequireAny(List<Feature> 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;
|
||||
}
|
||||
|
||||
}
|
@ -14,7 +14,7 @@ public class CheckboxTable extends JTable {
|
||||
|
||||
public CheckboxTable() {
|
||||
setShowGrid(false);
|
||||
setRowHeight(getRowHeight() + 4);
|
||||
setRowHeight((int) (Math.max(getRowHeight(), new JCheckBox().getPreferredSize().getHeight() - 2)));
|
||||
setIntercellSpacing(new Dimension(0, 0));
|
||||
setFillsViewportHeight(true);
|
||||
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
|
107
src/main/java/com/skcraft/launcher/swing/FeatureTableModel.java
Normal file
107
src/main/java/com/skcraft/launcher/swing/FeatureTableModel.java
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* SK's Minecraft Launcher
|
||||
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
|
||||
* Please see LICENSE.txt for license information.
|
||||
*/
|
||||
|
||||
package com.skcraft.launcher.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<Feature> features;
|
||||
|
||||
public FeatureTableModel(List<Feature> 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 "<html>" + SwingHelper.htmlEscape(feature.getName()) + getAddendum(feature) + "</html>";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String getAddendum(Feature feature) {
|
||||
if (feature.getRecommendation() == null) {
|
||||
return "";
|
||||
}
|
||||
switch (feature.getRecommendation()) {
|
||||
case STARRED:
|
||||
return " <span style=\"color: #3758DB\">" + _("features.starred") + "</span>";
|
||||
case AVOID:
|
||||
return " <span style=\"color: red\">" + _("features.avoid") + "</span>";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -14,7 +14,7 @@ public class InstanceTable extends JTable {
|
||||
|
||||
public InstanceTable() {
|
||||
setShowGrid(false);
|
||||
setRowHeight(Math.max(getRowHeight() + 4, 20 ));
|
||||
setRowHeight(Math.max(getRowHeight() + 4, 20));
|
||||
setIntercellSpacing(new Dimension(0, 0));
|
||||
setFillsViewportHeight(true);
|
||||
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
|
@ -6,17 +6,18 @@
|
||||
|
||||
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.install.FileMover;
|
||||
import com.skcraft.launcher.install.InstallLog;
|
||||
import com.skcraft.launcher.install.Installer;
|
||||
import com.skcraft.launcher.install.UpdateCache;
|
||||
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;
|
||||
@ -25,6 +26,7 @@ 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;
|
||||
@ -59,11 +61,13 @@ public abstract class BaseUpdater {
|
||||
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())
|
||||
@ -77,6 +81,29 @@ public abstract class BaseUpdater {
|
||||
manifest.setBaseUrl(instance.getManifestURL());
|
||||
}
|
||||
|
||||
final List<Feature> 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);
|
||||
}
|
||||
@ -92,17 +119,9 @@ public abstract class BaseUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Persistence.write(logPath, currentLog);
|
||||
} catch (IOException e) {
|
||||
log.log(Level.WARNING, "Failed to write install log", e);
|
||||
}
|
||||
|
||||
try {
|
||||
Persistence.write(cachePath, updateCache);
|
||||
} catch (IOException e) {
|
||||
log.log(Level.WARNING, "Failed to write update cache", e);
|
||||
}
|
||||
writeDataFile(logPath, currentLog);
|
||||
writeDataFile(cachePath, updateCache);
|
||||
writeDataFile(featuresPath, featuresCache);
|
||||
}
|
||||
});
|
||||
|
||||
@ -196,4 +215,13 @@ public abstract class BaseUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -82,6 +82,9 @@ public class Updater extends BaseUpdater implements Callable<Instance>, Progress
|
||||
String message = _("updater.updateRequiredButNoManifest");
|
||||
throw new LauncherException("Update required but no manifest", message);
|
||||
} else {
|
||||
instance.setUpdatePending(false);
|
||||
Persistence.commitAndForget(instance);
|
||||
|
||||
log.info("Can't update " + instance.getTitle() + ", but update is not required");
|
||||
return instance; // Can't update
|
||||
}
|
||||
|
@ -171,4 +171,12 @@ runner.corruptAssetsIndex={0} needs to be relaunched and updated because its ass
|
||||
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.
|
||||
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)
|
Loading…
Reference in New Issue
Block a user