mirror of
https://github.com/ProjectSWGCore/launcher.git
synced 2026-01-16 23:04:25 -05:00
Significant rewrite of the launcher - now with websockets
This commit is contained in:
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -4,9 +4,3 @@
|
||||
[submodule "forwarder"]
|
||||
path = forwarder
|
||||
url = https://github.com/ProjectSWGCore/forwarder.git
|
||||
[submodule "client-holocore"]
|
||||
path = client-holocore
|
||||
url = https://github.com/ProjectSWGCore/client-holocore.git
|
||||
[submodule "zero_allocation_hashing"]
|
||||
path = zero_allocation_hashing
|
||||
url = https://github.com/Josh-Larson/Zero-Allocation-Hashing.git
|
||||
|
||||
162
build.gradle.kts
162
build.gradle.kts
@@ -1,24 +1,23 @@
|
||||
//import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
application
|
||||
java
|
||||
idea
|
||||
kotlin("jvm") version "1.4.32"
|
||||
id("nebula.deb") version "8.4.1"
|
||||
id("nebula.rpm") version "8.4.1"
|
||||
id("edu.sc.seis.macAppBundle") version "2.3.0"
|
||||
id("org.beryx.jlink") version "2.23.6"
|
||||
kotlin("jvm") version "1.7.0"
|
||||
id("org.beryx.jlink") version "2.25.0"
|
||||
id("de.undercouch.download") version "5.0.5"
|
||||
}
|
||||
|
||||
// Note: define javaVersion, javaMajorVersion, javaHomeLinux, javaHomeMac, and javaHomeWindows
|
||||
// inside your gradle.properties file
|
||||
val javaVersion: String by project
|
||||
val javaMajorVersion: String by project
|
||||
val kotlinTargetJdk: String by project
|
||||
val javaHomeLinux: String by project
|
||||
val javaHomeMac: String by project
|
||||
val javaHomeWindows: String by project
|
||||
val javaVersion = "17"
|
||||
val javaMajorVersion = "17"
|
||||
val kotlinTargetJdk = "17"
|
||||
|
||||
val osName: String = System.getProperty("os.name")
|
||||
val platform: String = when {
|
||||
osName.startsWith("Mac", ignoreCase = true) -> "mac"
|
||||
osName.startsWith("Windows", ignoreCase = true) -> "win"
|
||||
osName.startsWith("Linux", ignoreCase = true) -> "linux"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
subprojects {
|
||||
ext {
|
||||
@@ -29,24 +28,22 @@ subprojects {
|
||||
}
|
||||
|
||||
group = "com.projectswg.launcher"
|
||||
version = "1.3.5"
|
||||
version = "2.0.0"
|
||||
|
||||
application {
|
||||
mainModule.set("com.projectswg.launcher")
|
||||
mainClass.set("com.projectswg.launcher.core.LauncherKt")
|
||||
mainClass.set("com.projectswg.launcher.LauncherKt")
|
||||
applicationDefaultJvmArgs = listOf("--add-opens", "javafx.graphics/javafx.scene=tornadofx")
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://dev.joshlarson.me/maven2")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
mavenCentral()
|
||||
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") }
|
||||
jcenter()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java.outputDir = File(java.outputDir.toString().replace("\\${File.separatorChar}java", ""))
|
||||
|
||||
dependencies {
|
||||
val jfxOptions = object {
|
||||
val group = "org.openjfx"
|
||||
@@ -54,25 +51,17 @@ sourceSets {
|
||||
val fxModules = arrayListOf("javafx-base", "javafx-graphics", "javafx-controls", "javafx-fxml", "javafx-swing", "javafx-web", "javafx-media")
|
||||
}
|
||||
jfxOptions.run {
|
||||
val osName = System.getProperty("os.name")
|
||||
val platform = when {
|
||||
osName.startsWith("Mac", ignoreCase = true) -> "mac"
|
||||
osName.startsWith("Windows", ignoreCase = true) -> "win"
|
||||
osName.startsWith("Linux", ignoreCase = true) -> "linux"
|
||||
else -> ""
|
||||
}
|
||||
fxModules.forEach {
|
||||
implementation("$group:$it:$version:$platform")
|
||||
}
|
||||
}
|
||||
|
||||
implementation(group="org.jetbrains", name="annotations", version="20.1.0")
|
||||
implementation(project(":pswgcommon"))
|
||||
implementation(project(":client-holocore"))
|
||||
implementation(project(":forwarder"))
|
||||
implementation(project(":zero_allocation_hashing"))
|
||||
implementation("javax.json:javax.json-api:1.1.4")
|
||||
implementation(group="me.joshlarson", name="fast-json", version="3.0.1")
|
||||
implementation(group="me.joshlarson", name="jlcommon-fx", version="1.0.3")
|
||||
implementation(group="me.joshlarson", name="jlcommon-fx", version="17.0.0")
|
||||
implementation(group="no.tornado", name="tornadofx", version="2.0.0-SNAPSHOT") {
|
||||
exclude(group="org.jetbrains.kotlin")
|
||||
}
|
||||
@@ -85,14 +74,7 @@ sourceSets {
|
||||
}
|
||||
test {
|
||||
dependencies {
|
||||
implementation(group="junit", name="junit", version="4.12")
|
||||
}
|
||||
}
|
||||
create("utility") {
|
||||
dependencies {
|
||||
implementation(group="org.bouncycastle", name="bcprov-jdk15on", version="1.60")
|
||||
implementation(group="me.joshlarson", name="fast-json", version="3.0.1")
|
||||
implementation(project(":zero_allocation_hashing"))
|
||||
testImplementation(group="junit", name="junit", version="4.12")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,94 +90,48 @@ java {
|
||||
modularity.inferModulePath.set(true)
|
||||
}
|
||||
|
||||
task("downloadJmods", de.undercouch.gradle.tasks.download.Download::class) {
|
||||
val zipPath = buildDir.absolutePath + "/jmods.zip"
|
||||
val unzipPath = buildDir.absolutePath + "/jmods"
|
||||
val baseUrl = "https://download2.gluonhq.com/openjfx/$javaVersion/"
|
||||
val platformUrl = when(platform) {
|
||||
"mac" -> "openjfx-${javaVersion}_osx-x64_bin-jmods.zip"
|
||||
"win" -> "openjfx-${javaVersion}_windows-x64_bin-jmods.zip"
|
||||
else -> "openjfx-${javaVersion}_linux-x64_bin-jmods.zip"
|
||||
}
|
||||
src(baseUrl + platformUrl)
|
||||
dest(zipPath)
|
||||
overwrite(false)
|
||||
tasks.getByName("jlink").dependsOn(this)
|
||||
|
||||
doLast {
|
||||
copy {
|
||||
from(zipTree(zipPath))
|
||||
into(unzipPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jlink {
|
||||
addOptions("--strip-debug", "--compress", "2", "--no-header-files", "--no-man-pages")
|
||||
javaHome.set(javaHomeLinux)
|
||||
targetPlatform("linux", javaHomeLinux)
|
||||
targetPlatform("mac", javaHomeMac)
|
||||
targetPlatform("windows", javaHomeWindows)
|
||||
forceMerge("kotlin-stdlib")
|
||||
addExtraModulePath(buildDir.absolutePath + "/jmods")
|
||||
|
||||
launcher {
|
||||
name = "projectswg"
|
||||
jvmArgs = listOf("--add-opens", "javafx.graphics/javafx.scene=tornadofx")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the JLink created JRE into a subdirectory in build/ that contains a /Contents/Home/jre directory.
|
||||
* This has to happen because the Mac App Bundle plugin relies on that structure unfortunately.
|
||||
*/
|
||||
val macJreLocation = "$projectDir/build/mock-mac-jre/Contents/Home"
|
||||
tasks.create<Copy>("createMacJREStructure") {
|
||||
dependsOn(tasks.named("jlink"))
|
||||
from("build/image/projectswg-mac")
|
||||
include("**/*")
|
||||
into("$macJreLocation/jre")
|
||||
}
|
||||
|
||||
macAppBundle {
|
||||
appName = "ProjectSWG"
|
||||
dmgName = "ProjectSWG"
|
||||
icon = "src/main/resources/graphics/ProjectSWGLaunchpad.icns"
|
||||
mainClassName = application.mainClass.get()
|
||||
jvmVersion = javaVersion
|
||||
jreHome = macJreLocation
|
||||
bundleJRE = true
|
||||
}
|
||||
|
||||
// Enforce that the JRE is copied with a Mac based structure
|
||||
tasks.named("bundleJRE") {
|
||||
dependsOn(tasks.named("createMacJREStructure"))
|
||||
}
|
||||
|
||||
tasks.create<com.netflix.gradle.plugins.deb.Deb>("linuxDeb") {
|
||||
dependsOn("jlink")
|
||||
release = "1"
|
||||
packageName = "projectswg"
|
||||
maintainer = "ProjectSWG"
|
||||
|
||||
preInstall(file("packaging/linux/preInstall.sh"))
|
||||
postInstall(file("packaging/linux/postInstall.sh"))
|
||||
preUninstall(file("packaging/linux/preUninstall.sh"))
|
||||
postUninstall(file("packaging/linux/postUninstall.sh"))
|
||||
|
||||
from ("build/image/projectswg-linux") {
|
||||
into("/opt/ProjectSWG")
|
||||
jpackage {
|
||||
imageName = "projectswg"
|
||||
installerName = "ProjectSWG"
|
||||
installerType = if (platform == "linux") "deb" else null
|
||||
}
|
||||
from ("packaging/linux") {
|
||||
into("/opt/ProjectSWG")
|
||||
}
|
||||
|
||||
link("/usr/share/applications/ProjectSWG.desktop", "/opt/ProjectSWG/ProjectSWG.desktop")
|
||||
}
|
||||
|
||||
tasks.create<com.netflix.gradle.plugins.rpm.Rpm>("linuxRpm") {
|
||||
dependsOn("jlink")
|
||||
release = "1"
|
||||
packageName = "projectswg"
|
||||
maintainer = "ProjectSWG"
|
||||
|
||||
preInstall(file("packaging/linux/preInstall.sh"))
|
||||
postInstall(file("packaging/linux/postInstall.sh"))
|
||||
preUninstall(file("packaging/linux/preUninstall.sh"))
|
||||
postUninstall(file("packaging/linux/postUninstall.sh"))
|
||||
|
||||
from ("build/image/projectswg-linux") {
|
||||
into("/opt/ProjectSWG")
|
||||
}
|
||||
from ("packaging/linux") {
|
||||
exclude("*.sh")
|
||||
into("/opt/ProjectSWG")
|
||||
}
|
||||
|
||||
link("/usr/share/applications/ProjectSWG.desktop", "/opt/ProjectSWG/ProjectSWG.desktop")
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = kotlinTargetJdk
|
||||
}
|
||||
destinationDir = sourceSets.main.get().java.outputDir
|
||||
destinationDirectory.set(File(destinationDirectory.get().asFile.path.replace("kotlin", "java")))
|
||||
}
|
||||
|
||||
Submodule client-holocore deleted from 242066cbb5
Submodule forwarder updated: 99b81aa05f...f892d4beea
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
269
gradlew
vendored
269
gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,67 +17,101 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# 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
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
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"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
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
|
||||
@@ -106,80 +140,95 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "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 or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# 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=`expr $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" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
Submodule pswgcommon updated: c60ac9f00c...d23749c5a0
@@ -1,2 +1,2 @@
|
||||
rootProject.name = "launcher"
|
||||
include("pswgcommon", "client-holocore", "forwarder", "tornadofx", "zero_allocation_hashing")
|
||||
include("pswgcommon", "forwarder", "tornadofx")
|
||||
|
||||
@@ -18,19 +18,19 @@
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core
|
||||
package com.projectswg.launcher
|
||||
|
||||
import com.projectswg.common.utilities.LocalUtilities
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.gui.NavigationView
|
||||
import com.projectswg.launcher.core.resources.gui.events.LauncherClosingEvent
|
||||
import com.projectswg.launcher.core.resources.gui.style.Style
|
||||
import com.projectswg.launcher.core.services.data.DataManager
|
||||
import com.projectswg.launcher.core.services.launcher.LauncherManager
|
||||
import com.projectswg.launcher.resources.gui.NavigationView
|
||||
import com.projectswg.launcher.resources.gui.events.LauncherClosingEvent
|
||||
import com.projectswg.launcher.resources.gui.style.Style
|
||||
import com.projectswg.launcher.services.data.DataManager
|
||||
import com.projectswg.launcher.services.launcher.LauncherManager
|
||||
import javafx.scene.image.Image
|
||||
import javafx.stage.Stage
|
||||
import me.joshlarson.jlcommon.control.IntentManager
|
||||
import me.joshlarson.jlcommon.control.Manager
|
||||
import me.joshlarson.jlcommon.control.ServiceBase
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import me.joshlarson.jlcommon.log.log_wrapper.ConsoleLogWrapper
|
||||
import me.joshlarson.jlcommon.log.log_wrapper.FileLogWrapper
|
||||
@@ -56,18 +56,28 @@ class Launcher
|
||||
class LauncherApp: App(NavigationView::class, Style::class) {
|
||||
|
||||
private val intentManager = IntentManager(Runtime.getRuntime().availableProcessors())
|
||||
private val services = listOf(DataManager(), LauncherManager())
|
||||
private val services: List<ServiceBase>
|
||||
|
||||
init {
|
||||
IntentManager.setInstance(intentManager)
|
||||
FX.localeProperty().bind(LauncherData.INSTANCE.general.localeProperty)
|
||||
FX.messages = ResourceBundle.getBundle("strings.strings", LauncherData.INSTANCE.general.locale)
|
||||
LauncherData.INSTANCE.general.localeProperty.addListener {_, _, locale ->
|
||||
FX.messages = ResourceBundle.getBundle("strings.strings", locale)
|
||||
FX.primaryStage.scene.reloadStylesheets()
|
||||
FX.primaryStage.scene.findUIComponents().forEach {
|
||||
FX.replaceComponent(it)
|
||||
try {
|
||||
services = listOf(DataManager(), LauncherManager())
|
||||
|
||||
IntentManager.setInstance(intentManager)
|
||||
FX.localeProperty().bind(com.projectswg.launcher.resources.data.LauncherData.INSTANCE.general.localeProperty)
|
||||
FX.messages = ResourceBundle.getBundle("strings.strings", com.projectswg.launcher.resources.data.LauncherData.INSTANCE.general.locale)
|
||||
com.projectswg.launcher.resources.data.LauncherData.INSTANCE.general.localeProperty.addListener { _, _, locale ->
|
||||
FX.messages = ResourceBundle.getBundle("strings.strings", locale)
|
||||
FX.primaryStage.scene.reloadStylesheets()
|
||||
FX.primaryStage.scene.findUIComponents().forEach {
|
||||
FX.replaceComponent(it)
|
||||
}
|
||||
}
|
||||
} catch (t: Error) {
|
||||
t.printStackTrace()
|
||||
throw t
|
||||
} catch (t: Exception) {
|
||||
t.printStackTrace()
|
||||
throw t
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
package com.projectswg.launcher.core
|
||||
package com.projectswg.launcher
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.announcements.WebsitePostFeed
|
||||
import com.projectswg.launcher.resources.data.announcements.WebsitePostFeed
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import me.joshlarson.jlcommon.log.log_wrapper.ConsoleLogWrapper
|
||||
import java.io.File
|
||||
@@ -1,91 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.data;
|
||||
|
||||
import com.projectswg.launcher.core.Launcher;
|
||||
import com.projectswg.launcher.core.resources.data.announcements.AnnouncementsData;
|
||||
import com.projectswg.launcher.core.resources.data.forwarder.ForwarderData;
|
||||
import com.projectswg.launcher.core.resources.data.general.GeneralData;
|
||||
import com.projectswg.launcher.core.resources.data.login.LoginData;
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateData;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
import tornadofx.FX;
|
||||
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
public enum LauncherData {
|
||||
INSTANCE;
|
||||
|
||||
public static final String VERSION = "1.3.4";
|
||||
public static final String UPDATE_ADDRESS = "login1.projectswg.com";
|
||||
|
||||
private final AnnouncementsData announcementsData;
|
||||
private final GeneralData generalData;
|
||||
private final LoginData loginData;
|
||||
private final UpdateData updateData;
|
||||
private final ForwarderData forwarderData;
|
||||
|
||||
LauncherData() {
|
||||
this.announcementsData = new AnnouncementsData();
|
||||
this.generalData = new GeneralData();
|
||||
this.loginData = new LoginData();
|
||||
this.updateData = new UpdateData();
|
||||
this.forwarderData = new ForwarderData();
|
||||
}
|
||||
|
||||
public Preferences getPreferences() {
|
||||
return Preferences.userNodeForPackage(Launcher.class);
|
||||
}
|
||||
|
||||
public Application getApplication() {
|
||||
return FX.Companion.getApplication();
|
||||
}
|
||||
|
||||
public Stage getStage() {
|
||||
return FX.Companion.getPrimaryStage();
|
||||
}
|
||||
|
||||
public AnnouncementsData getAnnouncements() {
|
||||
return announcementsData;
|
||||
}
|
||||
|
||||
public GeneralData getGeneral() {
|
||||
return generalData;
|
||||
}
|
||||
|
||||
public LoginData getLogin() {
|
||||
return loginData;
|
||||
}
|
||||
|
||||
public UpdateData getUpdate() {
|
||||
return updateData;
|
||||
}
|
||||
|
||||
public ForwarderData getForwarderData() {
|
||||
return forwarderData;
|
||||
}
|
||||
|
||||
public static LauncherData getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package com.projectswg.launcher.core.resources.data.announcements
|
||||
|
||||
|
||||
class CardData(val imageUrl: String, val title: String, val description: String, val link: String?)
|
||||
@@ -1,115 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.general;
|
||||
|
||||
import me.joshlarson.jlcommon.javafx.beans.ConcurrentBoolean;
|
||||
import me.joshlarson.jlcommon.javafx.beans.ConcurrentReference;
|
||||
import me.joshlarson.jlcommon.javafx.beans.ConcurrentString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class GeneralData {
|
||||
|
||||
private final ConcurrentBoolean sound;
|
||||
private final ConcurrentReference<LauncherTheme> theme;
|
||||
private final ConcurrentReference<Locale> locale;
|
||||
private final ConcurrentString wine;
|
||||
private final ConcurrentBoolean admin;
|
||||
|
||||
public GeneralData() {
|
||||
this.sound = new ConcurrentBoolean();
|
||||
this.theme = new ConcurrentReference<>(LauncherTheme.DEFAULT);
|
||||
this.locale = new ConcurrentReference<>(Locale.getDefault());
|
||||
this.wine = new ConcurrentString();
|
||||
this.admin = new ConcurrentBoolean();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConcurrentBoolean getSoundProperty() {
|
||||
return sound;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConcurrentReference<LauncherTheme> getThemeProperty() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConcurrentReference<Locale> getLocaleProperty() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConcurrentString getWineProperty() {
|
||||
return wine;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConcurrentBoolean getAdminProperty() {
|
||||
return admin;
|
||||
}
|
||||
|
||||
public boolean isSound() {
|
||||
return sound.get();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public LauncherTheme getTheme() {
|
||||
return theme.get();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Locale getLocale() {
|
||||
return locale.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getWine() {
|
||||
return wine.get();
|
||||
}
|
||||
|
||||
public boolean isAdmin() {
|
||||
return admin.get();
|
||||
}
|
||||
|
||||
public void setSound(boolean sound) {
|
||||
this.sound.set(sound);
|
||||
}
|
||||
|
||||
public void setTheme(@NotNull LauncherTheme theme) {
|
||||
this.theme.set(theme);
|
||||
}
|
||||
|
||||
public void setLocale(@NotNull Locale locale) {
|
||||
this.locale.set(locale);
|
||||
}
|
||||
|
||||
public void setWine(@Nullable String wine) {
|
||||
this.wine.set(wine);
|
||||
}
|
||||
|
||||
public void setAdmin(boolean admin) {
|
||||
this.admin.set(admin);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.general;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum LauncherTheme {
|
||||
DEFAULT ("projectswg");
|
||||
|
||||
private static final Map<String, LauncherTheme> TAG_TO_THEME = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (LauncherTheme theme : values()) {
|
||||
TAG_TO_THEME.put(theme.primaryTag, theme);
|
||||
for (String tag : theme.tags)
|
||||
TAG_TO_THEME.put(tag, theme);
|
||||
}
|
||||
}
|
||||
|
||||
private final String [] tags;
|
||||
private final String primaryTag;
|
||||
|
||||
LauncherTheme(String primaryTag, String ... tags) {
|
||||
this.tags = tags;
|
||||
this.primaryTag = primaryTag;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return primaryTag;
|
||||
}
|
||||
|
||||
public static LauncherTheme forThemeTag(String tag) {
|
||||
return TAG_TO_THEME.getOrDefault(tag, LauncherTheme.DEFAULT);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.login;
|
||||
|
||||
import me.joshlarson.jlcommon.javafx.beans.ConcurrentBoolean;
|
||||
import me.joshlarson.jlcommon.javafx.beans.ConcurrentString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class LoginServerInstanceInfo {
|
||||
|
||||
private final ConcurrentString loginStatus;
|
||||
private final ConcurrentString updateStatus;
|
||||
private final ConcurrentBoolean readyToPlay;
|
||||
|
||||
public LoginServerInstanceInfo() {
|
||||
this.loginStatus = new ConcurrentString("");
|
||||
this.updateStatus = new ConcurrentString("");
|
||||
this.readyToPlay = new ConcurrentBoolean();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConcurrentString getLoginStatusProperty() {
|
||||
return loginStatus;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConcurrentString getUpdateStatusProperty() {
|
||||
return updateStatus;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConcurrentBoolean getReadyToPlayProperty() {
|
||||
return readyToPlay;
|
||||
}
|
||||
|
||||
public String getLoginStatus() {
|
||||
return loginStatus.get();
|
||||
}
|
||||
|
||||
public String getUpdateStatus() {
|
||||
return updateStatus.get();
|
||||
}
|
||||
|
||||
public boolean isReadyToPlay() {
|
||||
return readyToPlay.get();
|
||||
}
|
||||
|
||||
public void setLoginStatus(@NotNull String loginStatus) {
|
||||
this.loginStatus.set(loginStatus);
|
||||
}
|
||||
|
||||
public void setUpdateStatus(@NotNull String updateStatus) {
|
||||
this.updateStatus.set(updateStatus);
|
||||
}
|
||||
|
||||
public void setReadyToPlay(boolean readyToPlay) {
|
||||
this.readyToPlay.set(readyToPlay);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.update;
|
||||
|
||||
import me.joshlarson.jlcommon.javafx.beans.ConcurrentSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
public class UpdateData {
|
||||
|
||||
private final ConcurrentSet<UpdateServer> servers;
|
||||
|
||||
public UpdateData() {
|
||||
this.servers = new ConcurrentSet<>(new CopyOnWriteArraySet<>());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConcurrentSet<UpdateServer> getServers() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
public void addServer(@NotNull UpdateServer server) {
|
||||
servers.add(server);
|
||||
}
|
||||
|
||||
public void removeServer(@NotNull UpdateServer server) {
|
||||
servers.remove(server);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.data.announcements.CardData
|
||||
import com.projectswg.launcher.core.resources.gui.style.CardStyle
|
||||
import javafx.scene.Cursor
|
||||
import javafx.scene.control.Tooltip
|
||||
import javafx.scene.layout.Priority
|
||||
import tornadofx.*
|
||||
|
||||
class Card : Fragment() {
|
||||
|
||||
val data: CardData by param()
|
||||
|
||||
override val root = vbox {
|
||||
importStylesheet(CardStyle::class)
|
||||
isFillWidth = true
|
||||
addClass(CardStyle.card)
|
||||
hgrow = Priority.ALWAYS
|
||||
vgrow = Priority.ALWAYS
|
||||
|
||||
imageview("file://"+data.imageUrl) {
|
||||
isPreserveRatio = true
|
||||
fitHeight = 108.0
|
||||
addClass(CardStyle.cardTitle)
|
||||
|
||||
fitWidthProperty().bind(this@vbox.widthProperty())
|
||||
|
||||
if (data.link != null) {
|
||||
// Clicking the image takes you to the link
|
||||
Tooltip.install(this, Tooltip(data.link))
|
||||
|
||||
cursor = Cursor.HAND
|
||||
setOnMouseClicked { LauncherData.INSTANCE.application.hostServices.showDocument(data.link) }
|
||||
}
|
||||
}
|
||||
label(data.title) {
|
||||
addClass(CardStyle.cardTitle)
|
||||
|
||||
maxWidthProperty().bind(this@vbox.widthProperty())
|
||||
}
|
||||
separator()
|
||||
textarea(data.description) {
|
||||
isEditable = false
|
||||
addClass(CardStyle.cardDescription)
|
||||
|
||||
maxWidthProperty().bind(this@vbox.widthProperty())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.announcements.CardData
|
||||
import com.projectswg.launcher.core.resources.gui.style.CardStyle
|
||||
import javafx.application.Platform
|
||||
import javafx.collections.ObservableList
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import tornadofx.*
|
||||
import kotlin.math.max
|
||||
|
||||
class CardContainer : Fragment() {
|
||||
|
||||
override val root = flowpane {
|
||||
importStylesheet(CardStyle::class)
|
||||
addClass(CardStyle.cardContainer)
|
||||
|
||||
minWidth = 300.0
|
||||
minHeight = 300.0
|
||||
}
|
||||
|
||||
val children: ObservableList<CardData> by param()
|
||||
|
||||
init {
|
||||
root.bindChildren(children) {
|
||||
val card = find<Card>(mapOf(Card::data to it)).root
|
||||
card.prefWidthProperty().bind(root.widthProperty().doubleBinding { max -> createCardSize(max?.toDouble() ?: 0.0) })
|
||||
card.prefHeightProperty().bind(root.heightProperty().doubleBinding { max -> createCardSize(max?.toDouble() ?: 0.0) })
|
||||
// card.prefWidth = createCardSize(root.width)
|
||||
// card.prefHeight = createCardSize(root.height)
|
||||
Platform.runLater { root.applyCss() }
|
||||
Log.t("Initializing card '${it.title}' with size ${card.prefWidth}x${card.prefHeight}")
|
||||
card
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCardSize(max: Double): Double {
|
||||
val count = max(1, (max / 300).toInt())
|
||||
return ((max - (count - 1) * 10) / count - 0.5).toInt().toDouble()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2020 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui
|
||||
|
||||
import com.projectswg.launcher.core.resources.gui.style.Style
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.data.login.LoginServer
|
||||
import com.projectswg.launcher.core.resources.gui.servers.ServerPlayCell
|
||||
import com.projectswg.launcher.core.resources.gui.servers.WebsitePostFeedList
|
||||
import javafx.beans.property.ReadOnlyStringWrapper
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.control.TableColumn
|
||||
import javafx.scene.layout.Priority
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import tornadofx.*
|
||||
import java.util.*
|
||||
|
||||
class ServerListView : View() {
|
||||
|
||||
private val feedList: WebsitePostFeedList by inject()
|
||||
|
||||
override val root = vbox {
|
||||
imageview(url = "/graphics/headers/server-table.png") {
|
||||
fitWidthProperty().bind(this@vbox.widthProperty())
|
||||
}
|
||||
tableview<LoginServer> {
|
||||
items = LauncherData.INSTANCE.login.serversProperty
|
||||
isFocusTraversable = false
|
||||
placeholder = label(messages["noServers"])
|
||||
setSortPolicy { _ -> Comparator.comparing<LoginServer, String> { it.name }; true }
|
||||
|
||||
column(messages["servers.column.name"], valueProvider={cellDataFeatures:TableColumn.CellDataFeatures<LoginServer, String> -> ReadOnlyStringWrapper(cellDataFeatures.value?.name) }).apply {
|
||||
prefWidth = COL_WIDTH_LARGE
|
||||
styleClass += "center-table-cell"
|
||||
}
|
||||
column(messages["servers.column.gameVersion"], valueProvider={cellDataFeatures:TableColumn.CellDataFeatures<LoginServer, String> -> cellDataFeatures.value?.updateServerProperty?.select { it.gameVersionProperty } ?: ReadOnlyStringWrapper("N/A") }).apply {
|
||||
prefWidth = COL_WIDTH_MEDIUM
|
||||
styleClass += "center-table-cell"
|
||||
}
|
||||
column(messages["servers.column.remoteStatus"], valueProvider={cellDataFeatures:TableColumn.CellDataFeatures<LoginServer, String> -> cellDataFeatures.value?.instanceInfo?.loginStatusProperty ?: ReadOnlyStringWrapper("") } ).apply {
|
||||
cellFormat {
|
||||
text = it
|
||||
toggleClass(Style.statusFail, it == "OFFLINE")
|
||||
toggleClass(Style.statusInProgress, it == FX.messages["servers.loginStatus.checking"] || it == "LOADING")
|
||||
toggleClass(Style.statusGood, it == "UP")
|
||||
}
|
||||
|
||||
prefWidth = COL_WIDTH_LARGE
|
||||
styleClass += "center-table-cell"
|
||||
}
|
||||
column(messages["servers.column.localStatus"], valueProvider={cellDataFeatures:TableColumn.CellDataFeatures<LoginServer, String> -> cellDataFeatures.value?.instanceInfo?.updateStatusProperty ?: ReadOnlyStringWrapper("") } ).apply {
|
||||
cellFormat {
|
||||
text = messages[it]
|
||||
}
|
||||
prefWidth = COL_WIDTH_LARGE
|
||||
styleClass += "center-table-cell"
|
||||
}
|
||||
column(messages["servers.column.play"], LoginServer::class) {
|
||||
// setCellFactory { ServerPlayCell() }
|
||||
cellFragment(ServerPlayCell::class)
|
||||
setCellValueFactory { param -> SimpleObjectProperty(param.value) }
|
||||
|
||||
prefWidth = COL_WIDTH_LARGE
|
||||
styleClass.add("center-table-cell")
|
||||
}
|
||||
}
|
||||
region { prefHeight = 5.0 }
|
||||
hbox {
|
||||
isFillWidth = true
|
||||
|
||||
hboxConstraints {
|
||||
this.marginRight = 5.0
|
||||
}
|
||||
|
||||
vbox leftBox@ {
|
||||
// TODO: RSS-based list of new posts
|
||||
prefWidthProperty().bind(this@hbox.widthProperty().divide(2).subtract(5))
|
||||
|
||||
this += feedList.root
|
||||
}
|
||||
vbox rightBox@ {
|
||||
prefWidthProperty().bind(this@hbox.widthProperty().divide(2).subtract(5))
|
||||
|
||||
// Login container
|
||||
form {
|
||||
val username = SimpleStringProperty()
|
||||
val password = SimpleStringProperty()
|
||||
fieldset(messages["servers.login.form.title"]) {
|
||||
field(messages["servers.login.form.username"]) {
|
||||
textfield(username)
|
||||
}
|
||||
field(messages["servers.login.form.password"]) {
|
||||
passwordfield(password)
|
||||
}
|
||||
}
|
||||
button(messages["servers.login.form.submit"]) {
|
||||
action {
|
||||
Log.i("Logging in with %s / %s", username.get(), password.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
region { minHeight = 5.0; maxHeight = 5.0 }
|
||||
separator()
|
||||
region { minHeight = 5.0; maxHeight = 5.0 }
|
||||
|
||||
gridpane {
|
||||
this.hgap = 5.0
|
||||
this.vgap = 5.0
|
||||
|
||||
row {
|
||||
button(messages["servers.login.buttons.website"]) {
|
||||
useMaxWidth = true
|
||||
gridpaneColumnConstraints {
|
||||
hgrow = Priority.ALWAYS
|
||||
}
|
||||
action {
|
||||
// TODO add hyperlink
|
||||
}
|
||||
}
|
||||
button(messages["servers.login.buttons.create_account"]) {
|
||||
useMaxWidth = true
|
||||
gridpaneColumnConstraints {
|
||||
hgrow = Priority.ALWAYS
|
||||
}
|
||||
action {
|
||||
// TODO: this, somehow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
row {
|
||||
button(messages["servers.login.buttons.configuration"]) {
|
||||
useMaxWidth = true
|
||||
gridpaneColumnConstraints {
|
||||
hgrow = Priority.ALWAYS
|
||||
}
|
||||
action {
|
||||
// TODO add hyperlink
|
||||
}
|
||||
}
|
||||
button(messages["servers.login.buttons.server_list"]) {
|
||||
useMaxWidth = true
|
||||
gridpaneColumnConstraints {
|
||||
hgrow = Priority.ALWAYS
|
||||
}
|
||||
action {
|
||||
// TODO: this, somehow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
region { vgrow = Priority.ALWAYS }
|
||||
|
||||
label("%s: %s".format(messages["servers.login.launcher_version"], LauncherData.VERSION)) {
|
||||
prefWidthProperty().bind(this@rightBox.widthProperty())
|
||||
alignment = Pos.BASELINE_RIGHT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val COL_WIDTH_MEDIUM = 110.0
|
||||
private const val COL_WIDTH_LARGE = 150.0
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.projectswg.launcher.core.resources.gui.events
|
||||
|
||||
import tornadofx.FXEvent
|
||||
|
||||
object LauncherClosingEvent : FXEvent()
|
||||
@@ -1,127 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui.servers
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.data.login.LoginServer
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer.UpdateServerStatus.*
|
||||
import com.projectswg.launcher.core.resources.gui.admin.AdminDisplay
|
||||
import com.projectswg.launcher.core.resources.game.GameInstance
|
||||
import com.projectswg.launcher.core.resources.intents.CancelDownloadIntent
|
||||
import com.projectswg.launcher.core.resources.intents.DownloadPatchIntent
|
||||
import com.projectswg.launcher.core.resources.intents.GameLaunchedIntent
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper
|
||||
import javafx.beans.property.ReadOnlyStringWrapper
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.beans.value.ObservableStringValue
|
||||
import javafx.geometry.Insets
|
||||
import javafx.geometry.Pos
|
||||
import javafx.util.converter.NumberStringConverter
|
||||
import tornadofx.*
|
||||
|
||||
class ServerPlayCell : TableCellFragment<LoginServer, LoginServer>() {
|
||||
|
||||
private val loginServerProperty = itemProperty
|
||||
private val updateServerProperty = loginServerProperty.select { it.updateServerProperty }
|
||||
private val updateServerStatusProperty = updateServerProperty.select { it.statusProperty }
|
||||
|
||||
override val root = vbox {
|
||||
spacing = 5.0
|
||||
padding = Insets(5.0)
|
||||
alignment = Pos.CENTER
|
||||
styleClass.add("server-play-cell")
|
||||
|
||||
button(updateServerStatusProperty.select { ReadOnlyStringWrapper(getButtonText(it)) }) {
|
||||
disableProperty().bind(updateServerStatusProperty.select { ReadOnlyBooleanWrapper(getButtonDisabled(it)) })
|
||||
setOnAction { onButtonClicked() }
|
||||
}
|
||||
|
||||
label(updateServerStatusProperty.select { getLabelText(it) }) {
|
||||
managedProperty().bind(updateServerStatusProperty.select { ReadOnlyBooleanWrapper(getLabelVisible(it)) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun getButtonText(status: UpdateServer.UpdateServerStatus?): String {
|
||||
return when (status) {
|
||||
SCANNING, UNKNOWN, READY -> messages["servers.play.play"]
|
||||
REQUIRES_DOWNLOAD -> messages["servers.play.update"]
|
||||
DOWNLOADING -> messages["servers.play.cancel"]
|
||||
null -> ""
|
||||
}
|
||||
}
|
||||
private fun getButtonDisabled(status: UpdateServer.UpdateServerStatus?): Boolean = status == SCANNING
|
||||
|
||||
private fun onButtonClicked() {
|
||||
when (updateServerStatusProperty.value) {
|
||||
UNKNOWN, READY -> {
|
||||
val loginServer = loginServerProperty.value ?: return
|
||||
runAsync {
|
||||
val gameInstance = GameInstance(loginServer)
|
||||
gameInstance.start()
|
||||
GameLaunchedIntent(gameInstance).broadcast()
|
||||
gameInstance
|
||||
// } ui {
|
||||
// if (LauncherData.INSTANCE.general.isAdmin) {
|
||||
// find<AdminDisplay>(AdminDisplay::forwarder to it.forwarder).openWindow()
|
||||
// TODO: Fix admin display
|
||||
// }
|
||||
}
|
||||
}
|
||||
REQUIRES_DOWNLOAD -> DownloadPatchIntent(updateServerProperty.value ?: return).broadcast()
|
||||
DOWNLOADING -> CancelDownloadIntent(updateServerProperty.value ?: return).broadcast()
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLabelText(status: UpdateServer.UpdateServerStatus?): ObservableStringValue {
|
||||
return when (status) {
|
||||
null, UNKNOWN, READY, SCANNING -> ReadOnlyStringWrapper(messages["servers.action_info.empty"])
|
||||
REQUIRES_DOWNLOAD -> ReadOnlyStringWrapper(calculateDownloadSize(updateServerProperty.value?.requiredFiles ?: return ReadOnlyStringWrapper("")) + " " + messages["servers.action_info.required"])
|
||||
DOWNLOADING -> {
|
||||
val wrapper = SimpleStringProperty()
|
||||
wrapper.bindBidirectional(updateServerProperty.select { it.downloadProgressProperty }, NumberStringConverter("0.00% ${messages["servers.action_info.progress"]}"))
|
||||
wrapper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLabelVisible(status: UpdateServer.UpdateServerStatus?): Boolean {
|
||||
return status != UNKNOWN && status != READY && status != SCANNING
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val SIZE_SUFFIX = listOf("B", "kB", "MB", "GB", "TB", "PB")
|
||||
|
||||
private fun calculateDownloadSize(files: Collection<UpdateServer.RequiredFile>): String {
|
||||
var totalSize = files.stream().mapToLong { it.length }.sum().toDouble()
|
||||
for ((i, suffix) in SIZE_SUFFIX.withIndex()) {
|
||||
if (i != 0)
|
||||
totalSize /= 1024.0
|
||||
if (totalSize < 1024)
|
||||
return String.format("%.2f%s", totalSize, suffix)
|
||||
}
|
||||
return String.format("%.2f%s", totalSize, SIZE_SUFFIX[SIZE_SUFFIX.size - 1])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui.settings
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.data.forwarder.ForwarderData
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.control.Button
|
||||
import javafx.scene.control.TextField
|
||||
import javafx.util.converter.NumberStringConverter
|
||||
import tornadofx.View
|
||||
|
||||
class SettingsForwarderView : View() {
|
||||
|
||||
override val root: Parent by fxml()
|
||||
private val sendIntervalTextField: TextField by fxid()
|
||||
private val sendMaxTextField: TextField by fxid()
|
||||
private val resetButton: Button by fxid()
|
||||
|
||||
init {
|
||||
sendIntervalTextField.text = LauncherData.INSTANCE.forwarderData.sendInterval.toString()
|
||||
sendMaxTextField.text = LauncherData.INSTANCE.forwarderData.sendMax.toString()
|
||||
sendIntervalTextField.textProperty().bindBidirectional(LauncherData.INSTANCE.forwarderData.sendIntervalProperty, NumberStringConverter())
|
||||
sendMaxTextField.textProperty().bindBidirectional(LauncherData.INSTANCE.forwarderData.sendMaxProperty, NumberStringConverter())
|
||||
|
||||
resetButton.setOnAction {
|
||||
sendIntervalTextField.text = ForwarderData.DEFAULT_SEND_INTERVAL.toString()
|
||||
sendMaxTextField.text = ForwarderData.DEFAULT_SEND_MAX.toString()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui.settings
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.data.general.LauncherTheme
|
||||
import com.projectswg.launcher.core.resources.gui.createGlyph
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.control.Button
|
||||
import javafx.scene.control.CheckBox
|
||||
import javafx.scene.control.ComboBox
|
||||
import javafx.scene.control.TextField
|
||||
import javafx.stage.FileChooser
|
||||
import tornadofx.View
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class SettingsGeneralView : View() {
|
||||
|
||||
override val root: Parent by fxml()
|
||||
private val soundCheckbox: CheckBox by fxid()
|
||||
private val themeComboBox: ComboBox<LauncherTheme> by fxid()
|
||||
private val localeComboBox: ComboBox<Locale> by fxid()
|
||||
private val wineTextField: TextField by fxid()
|
||||
private val wineSelectionButton: Button by fxid()
|
||||
private val adminCheckBox: CheckBox by fxid()
|
||||
|
||||
init {
|
||||
val data = LauncherData.INSTANCE.general
|
||||
|
||||
wineSelectionButton.graphic = FontAwesomeIcon.FOLDER_ALT.createGlyph()
|
||||
|
||||
themeComboBox.items.setAll(*LauncherTheme.values())
|
||||
localeComboBox.items.setAll(Locale.ENGLISH, Locale.GERMAN)
|
||||
|
||||
soundCheckbox.selectedProperty().bindBidirectional(data.soundProperty)
|
||||
themeComboBox.valueProperty().bindBidirectional(data.themeProperty)
|
||||
localeComboBox.valueProperty().bindBidirectional(data.localeProperty)
|
||||
wineTextField.textProperty().bindBidirectional(data.wineProperty)
|
||||
wineSelectionButton.setOnAction { this.processWineSelectionButtonAction() }
|
||||
adminCheckBox.selectedProperty().bindBidirectional(data.adminProperty)
|
||||
}
|
||||
|
||||
private fun processWineSelectionButtonAction() {
|
||||
val selection = chooseOpenFile("Choose Wine Path") ?: return
|
||||
try {
|
||||
wineTextField.text = selection.canonicalPath
|
||||
} catch (ex: IOException) {
|
||||
wineTextField.text = selection.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseOpenFile(title: String): File? {
|
||||
val fileChooser = FileChooser()
|
||||
fileChooser.title = title
|
||||
val file = fileChooser.showOpenDialog(LauncherData.INSTANCE.stage)
|
||||
return if (file == null || !file.isFile) null else file
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui.settings
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.data.login.LoginServer
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.core.resources.gui.createGlyph
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import javafx.beans.binding.BooleanBinding
|
||||
import javafx.beans.property.ObjectProperty
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.control.*
|
||||
import javafx.scene.control.skin.TextFieldSkin
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.util.converter.NumberStringConverter
|
||||
import tornadofx.View
|
||||
import tornadofx.select
|
||||
|
||||
|
||||
class SettingsLoginView : View() {
|
||||
|
||||
override val root: Parent by fxml()
|
||||
private val nameComboBox: ComboBox<LoginServer> by fxid()
|
||||
private val addressTextField: TextField by fxid()
|
||||
private val portTextField: TextField by fxid()
|
||||
private val usernameTextField: TextField by fxid()
|
||||
private val passwordField: PasswordField by fxid()
|
||||
private val hidePasswordButton: ToggleButton by fxid()
|
||||
private val updateServerComboBox: ComboBox<UpdateServer> by fxid()
|
||||
private val verifyServerCheckBox: CheckBox by fxid()
|
||||
private val enableEncryptionCheckBox: CheckBox by fxid()
|
||||
|
||||
init {
|
||||
val passwordSkin = PasswordFieldSkin(passwordField, hidePasswordButton.selectedProperty().not())
|
||||
passwordField.skin = passwordSkin
|
||||
hidePasswordButton.isSelected = false
|
||||
hidePasswordButton.graphicProperty().bind(hidePasswordButton.selectedProperty().select { if (it) createGlyph(FontAwesomeIcon.EYE_SLASH) else createGlyph(FontAwesomeIcon.EYE) })
|
||||
hidePasswordButton.selectedProperty().addListener { _, _, _ -> passwordField.text = passwordField.text }
|
||||
|
||||
// TODO: Add/remove login servers
|
||||
addressTextField.textProperty().bindBidirectional(nameComboBox.valueProperty().select { it.addressProperty })
|
||||
portTextField.textProperty().bindBidirectional(nameComboBox.valueProperty().select { it.portProperty }, NumberStringConverter("#"))
|
||||
usernameTextField.textProperty().bindBidirectional(nameComboBox.valueProperty().select { it.usernameProperty })
|
||||
passwordField.textProperty().bindBidirectional(nameComboBox.valueProperty().select { it.passwordProperty })
|
||||
updateServerComboBox.valueProperty().bindBidirectional(nameComboBox.valueProperty().select { it.updateServerProperty })
|
||||
verifyServerCheckBox.selectedProperty().bindBidirectional(nameComboBox.valueProperty().select { it.verifyServerProperty })
|
||||
enableEncryptionCheckBox.selectedProperty().bindBidirectional(nameComboBox.valueProperty().select { it.enableEncryptionProperty })
|
||||
|
||||
nameComboBox.items = FXCollections.observableArrayList(LauncherData.INSTANCE.login.servers)
|
||||
updateServerComboBox.items = FXCollections.observableArrayList(LauncherData.INSTANCE.update.servers)
|
||||
|
||||
nameComboBox.value = nameComboBox.items.getOrNull(0)
|
||||
}
|
||||
|
||||
private fun createGlyph(icon: FontAwesomeIcon): ObjectProperty<Node> {
|
||||
val node = icon.createGlyph(fill = Color.BLACK)
|
||||
return ReadOnlyObjectWrapper(node)
|
||||
}
|
||||
|
||||
inner class PasswordFieldSkin(passwordField: PasswordField, private val hiddenProperty: BooleanBinding?) : TextFieldSkin(passwordField) {
|
||||
|
||||
override fun maskText(txt: String): String = if (hiddenProperty?.value != false) "\u25CF".repeat(txt.length) else txt
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui.settings
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.core.resources.game.ProcessExecutor
|
||||
import com.projectswg.launcher.core.resources.gui.createGlyph
|
||||
import com.projectswg.launcher.core.resources.intents.RequestScanIntent
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.event.EventHandler
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.control.Button
|
||||
import javafx.scene.control.ComboBox
|
||||
import javafx.scene.control.TextField
|
||||
import javafx.stage.DirectoryChooser
|
||||
import javafx.util.converter.NumberStringConverter
|
||||
import tornadofx.View
|
||||
import tornadofx.select
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class SettingsUpdateView: View() {
|
||||
|
||||
override val root: Parent by fxml()
|
||||
private val nameComboBox: ComboBox<UpdateServer> by fxid()
|
||||
private val addressTextField: TextField by fxid()
|
||||
private val portTextField: TextField by fxid()
|
||||
private val basePathTextField: TextField by fxid()
|
||||
private val localPathTextField: TextField by fxid()
|
||||
private val scanButton: Button by fxid()
|
||||
private val clientOptionsButton: Button by fxid()
|
||||
private val localPathSelectionButton: Button by fxid()
|
||||
|
||||
private val currentDirectory: File
|
||||
get() {
|
||||
val server = nameComboBox.value ?: return File(".")
|
||||
val localPathString = server.localPath
|
||||
if (localPathString.isEmpty())
|
||||
return File(".")
|
||||
val localPath = File(localPathString)
|
||||
return if (!localPath.isDirectory) File(".") else localPath
|
||||
}
|
||||
|
||||
init {
|
||||
localPathSelectionButton.graphic = FontAwesomeIcon.FOLDER_ALT.createGlyph()
|
||||
|
||||
// TODO: Add/remove login servers
|
||||
scanButton.onAction = EventHandler { this.processScanButtonAction() }
|
||||
clientOptionsButton.onAction = EventHandler { this.processClientOptionsButtonAction() }
|
||||
|
||||
addressTextField.textProperty().bindBidirectional(nameComboBox.valueProperty().select { it.addressProperty })
|
||||
portTextField.textProperty().bindBidirectional(nameComboBox.valueProperty().select { it.portProperty }, NumberStringConverter("#"))
|
||||
basePathTextField.textProperty().bindBidirectional(nameComboBox.valueProperty().select { it.basePathProperty })
|
||||
localPathTextField.textProperty().bindBidirectional(nameComboBox.valueProperty().select { it.localPathProperty })
|
||||
localPathSelectionButton.onAction = EventHandler { this.processLocalPathSelectionButtonAction() }
|
||||
|
||||
nameComboBox.items = FXCollections.observableArrayList(LauncherData.INSTANCE.update.servers)
|
||||
nameComboBox.value = nameComboBox.items.getOrNull(0)
|
||||
}
|
||||
|
||||
private fun processScanButtonAction() {
|
||||
val server = nameComboBox.value ?: return
|
||||
RequestScanIntent(server).broadcast()
|
||||
}
|
||||
|
||||
private fun processClientOptionsButtonAction() {
|
||||
val server = nameComboBox.value ?: return
|
||||
ProcessExecutor.INSTANCE.buildProcess(server, "SwgClientSetup_r.exe")
|
||||
}
|
||||
|
||||
private fun processLocalPathSelectionButtonAction() {
|
||||
val selection = chooseOpenDirectory("Choose Local Installation Path", currentDirectory) ?: return
|
||||
try {
|
||||
localPathTextField.text = selection.canonicalPath
|
||||
} catch (ex: IOException) {
|
||||
localPathTextField.text = selection.absolutePath
|
||||
}
|
||||
|
||||
val server = nameComboBox.value ?: return
|
||||
RequestScanIntent(server).broadcast()
|
||||
}
|
||||
|
||||
private fun chooseOpenDirectory(title: String, currentDirectory: File): File? {
|
||||
val directoryChooser = DirectoryChooser()
|
||||
directoryChooser.title = title
|
||||
directoryChooser.initialDirectory = currentDirectory
|
||||
val file = directoryChooser.showDialog(LauncherData.INSTANCE.stage)
|
||||
return if (file == null || !file.isDirectory) null else file
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.projectswg.launcher.core.resources.gui.style
|
||||
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.control.ScrollPane
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.text.FontWeight
|
||||
import tornadofx.*
|
||||
|
||||
class CardStyle : Stylesheet() {
|
||||
companion object {
|
||||
val cardContainer by cssclass()
|
||||
val card by cssclass()
|
||||
val cardContent by cssclass()
|
||||
val cardImage by cssclass()
|
||||
val cardTitle by cssclass()
|
||||
val cardDescription by cssclass()
|
||||
}
|
||||
|
||||
init {
|
||||
cardContainer {
|
||||
hgap = 10.px
|
||||
vgap = 10.px
|
||||
}
|
||||
card {
|
||||
backgroundColor += c("#454545")
|
||||
vBarPolicy = ScrollPane.ScrollBarPolicy.AS_NEEDED
|
||||
hBarPolicy = ScrollPane.ScrollBarPolicy.NEVER
|
||||
}
|
||||
cardContent {
|
||||
vgap = 5.px
|
||||
alignment = Pos.CENTER
|
||||
}
|
||||
cardImage {
|
||||
alignment = Pos.CENTER
|
||||
}
|
||||
cardTitle {
|
||||
fontWeight = FontWeight.BOLD
|
||||
textFill = Color.WHITE
|
||||
wrapText = true
|
||||
fontSize = 14.px
|
||||
backgroundColor += c("#313131")
|
||||
padding = box(5.px)
|
||||
}
|
||||
cardDescription {
|
||||
fontSize = 12.px
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2020 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui.style
|
||||
|
||||
import javafx.scene.paint.Color
|
||||
import tornadofx.*
|
||||
|
||||
class Style : Stylesheet() {
|
||||
companion object {
|
||||
val statusNormal by cssclass()
|
||||
val statusFail by cssclass()
|
||||
val statusInProgress by cssclass()
|
||||
val statusGood by cssclass()
|
||||
|
||||
val background by cssclass()
|
||||
}
|
||||
|
||||
init {
|
||||
statusNormal {
|
||||
s(text) {
|
||||
fill = Color.WHITE
|
||||
}
|
||||
}
|
||||
statusFail {
|
||||
s(text) {
|
||||
fill = c("#FF0000")
|
||||
}
|
||||
}
|
||||
statusInProgress {
|
||||
s(text) {
|
||||
fill = Color.YELLOW
|
||||
}
|
||||
}
|
||||
statusGood {
|
||||
s(text) {
|
||||
fill = c("#00FF00")
|
||||
}
|
||||
}
|
||||
background {
|
||||
cell {
|
||||
and(even) {
|
||||
backgroundColor += c("#4e4e4e")
|
||||
and(hover) {
|
||||
backgroundColor += c("#404040")
|
||||
}
|
||||
}
|
||||
and(odd) {
|
||||
backgroundColor += c("#484848")
|
||||
and(hover) {
|
||||
backgroundColor += c("#404040")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.pipeline;
|
||||
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class Pipeline {
|
||||
|
||||
public static <T> PipelineCompiler<T> compile(String name) {
|
||||
return new PipelineCompiler<>(name);
|
||||
}
|
||||
|
||||
public static <T> PipelineExecutor<T> execute(String name) {
|
||||
return new PipelineExecutor<>(name);
|
||||
}
|
||||
|
||||
public static class PipelineCompiler<T> {
|
||||
|
||||
private final List<Predicate<T>> stages;
|
||||
private final String name;
|
||||
|
||||
public PipelineCompiler(String name) {
|
||||
this.stages = new CopyOnWriteArrayList<>();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public PipelineCompiler<T> next(Predicate<T> stage) {
|
||||
stages.add(stage);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PipelineCompiler<T> next(Consumer<T> stage) {
|
||||
return next(in -> {stage.accept(in); return true; });
|
||||
}
|
||||
|
||||
public PipelineCompiler<T> next(Runnable stage) {
|
||||
return next(in -> { stage.run(); return true; });
|
||||
}
|
||||
|
||||
public void execute(T input) {
|
||||
int stageIndex = 0;
|
||||
try {
|
||||
for (Predicate<T> stage : stages) {
|
||||
if (!stage.test(input))
|
||||
return;
|
||||
stageIndex++;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e("Pipeline '%s' failed during stage index %d!", name, stageIndex);
|
||||
Log.e(t);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class PipelineExecutor<T> {
|
||||
|
||||
private final String name;
|
||||
private boolean terminated;
|
||||
|
||||
public PipelineExecutor() {
|
||||
this("");
|
||||
}
|
||||
|
||||
public PipelineExecutor(String name) {
|
||||
this.name = name;
|
||||
this.terminated = false;
|
||||
}
|
||||
|
||||
public PipelineExecutor<T> execute(Predicate<T> stage, T t) {
|
||||
if (terminated)
|
||||
return this;
|
||||
try {
|
||||
stage.test(t);
|
||||
} catch (Throwable ex) {
|
||||
Log.e("Pipeline '%s' failed!", name);
|
||||
Log.e(ex);
|
||||
terminated = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public PipelineExecutor<T> execute(Consumer<T> stage, T t) {
|
||||
if (terminated)
|
||||
return this;
|
||||
try {
|
||||
stage.accept(t);
|
||||
} catch (Throwable ex) {
|
||||
Log.e("Pipeline '%s' failed!", name);
|
||||
Log.e(ex);
|
||||
terminated = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public PipelineExecutor<T> execute(Runnable stage) {
|
||||
if (terminated)
|
||||
return this;
|
||||
try {
|
||||
stage.run();
|
||||
} catch (Throwable t) {
|
||||
Log.e("Pipeline '%s' failed!", name);
|
||||
Log.e(t);
|
||||
terminated = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.pipeline;
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer;
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer.RequiredFile;
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer.UpdateServerStatus;
|
||||
import javafx.application.Platform;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
import me.joshlarson.json.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class UpdateServerUpdater {
|
||||
|
||||
private UpdateServerUpdater() {
|
||||
|
||||
}
|
||||
|
||||
public static void update(UpdateServer server) {
|
||||
UpdateServerDownloaderInfo info = new UpdateServerDownloaderInfo(server);
|
||||
if (!updateFileList(info))
|
||||
return;
|
||||
filterValidFiles(info);
|
||||
updateServerStatus(info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage 1: Download the file list from the update server, or fall back on the local copy. If neither are accessible, fail.
|
||||
*/
|
||||
private static boolean updateFileList(UpdateServerDownloaderInfo info) {
|
||||
Log.t("Retrieving latest file list from %s...", info.getAddress());
|
||||
File localFileList = new File(info.getLocalPath(), "files.json");
|
||||
List<Object> files;
|
||||
try (JSONInputStream in = new JSONInputStream(createURL(info, "files.json").openConnection().getInputStream())) {
|
||||
files = in.readArray();
|
||||
try (JSONOutputStream out = new JSONOutputStream(new FileOutputStream(localFileList))) {
|
||||
out.writeArray(files);
|
||||
} catch (IOException e) {
|
||||
Log.e("Failed to write updated file list to disk for update server %s (%s: %s)", info.getName(), e.getClass().getName(), e.getMessage());
|
||||
}
|
||||
} catch (IOException | JSONException e) {
|
||||
Log.w("Failed to retrieve latest file list for update server %s (%s: %s). Falling back on local copy...", e.getClass().getName(), e.getMessage(), info.getName());
|
||||
try (JSONInputStream in = new JSONInputStream(new FileInputStream(localFileList))) {
|
||||
files = in.readArray();
|
||||
} catch (JSONException | IOException t) {
|
||||
Log.e("Failed to read file list from disk on update server %s with path %s. Aborting update.", info.getName(), localFileList);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
info.setFiles(files.stream().filter(JSONObject.class::isInstance).map(JSONObject.class::cast).map(obj -> jsonObjectToRequiredFile(info, obj)).collect(Collectors.toList()));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage 2: Scan each file and only keep the ones that need to be downloaded.
|
||||
*/
|
||||
private static void filterValidFiles(UpdateServerDownloaderInfo info) {
|
||||
List<RequiredFile> files = Objects.requireNonNull(info.getFiles(), "File list was not read correctly");
|
||||
Log.d("%d known files. Scanning...", files.size());
|
||||
int total = files.size();
|
||||
Platform.runLater(() -> info.getServer().setStatus(UpdateServerStatus.SCANNING));
|
||||
files.removeIf(UpdateServerUpdater::isValidFile);
|
||||
int valid = total - files.size();
|
||||
Log.d("Completed scan of update server %s. %d of %d valid.", info.getName(), valid, total);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage 3: Update the UpdateServer status and the required files.
|
||||
*/
|
||||
private static void updateServerStatus(UpdateServerDownloaderInfo info) {
|
||||
List<RequiredFile> serverList = info.getServer().getRequiredFiles();
|
||||
List<RequiredFile> updateList = info.getFiles();
|
||||
UpdateServerStatus updateStatus = updateList.isEmpty() ? UpdateServerStatus.READY : UpdateServerStatus.REQUIRES_DOWNLOAD;
|
||||
|
||||
serverList.clear();
|
||||
serverList.addAll(updateList);
|
||||
Platform.runLater(() -> info.getServer().setStatus(updateStatus));
|
||||
Log.d("Setting update server '%s' status to %s", info.getName(), updateStatus);
|
||||
}
|
||||
|
||||
private static boolean isValidFile(RequiredFile file) {
|
||||
File localFile = file.getLocalPath();
|
||||
long length = localFile.length();
|
||||
return localFile.isFile() && length == file.getLength();
|
||||
}
|
||||
|
||||
private static RequiredFile jsonObjectToRequiredFile(UpdateServerDownloaderInfo info, JSONObject obj) {
|
||||
String path = obj.getString("path");
|
||||
try {
|
||||
return new RequiredFile(new File(info.getLocalPath(), path), createURL(info, path), obj.getLong("length"), obj.getLong("xxhash"));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static URL createURL(UpdateServerDownloaderInfo info, String path) throws MalformedURLException {
|
||||
String basePath = info.getBasePath();
|
||||
while (basePath.endsWith("/"))
|
||||
basePath = basePath.substring(0, basePath.length()-1);
|
||||
if (!path.startsWith("/"))
|
||||
path = "/" + path;
|
||||
basePath += path;
|
||||
return new URL("http", info.getAddress(), info.getPort(), basePath);
|
||||
}
|
||||
|
||||
private static class UpdateServerDownloaderInfo {
|
||||
|
||||
private final UpdateServer server;
|
||||
private final String updateServerName;
|
||||
private final String updateServerAddress;
|
||||
private final int updateServerPort;
|
||||
private final String updateServerBasePath;
|
||||
private final File updateServerLocalPath;
|
||||
|
||||
private List<RequiredFile> files;
|
||||
|
||||
public UpdateServerDownloaderInfo(UpdateServer server) {
|
||||
this.server = server;
|
||||
this.updateServerName = server.getName();
|
||||
this.updateServerAddress = server.getAddress();
|
||||
this.updateServerPort = server.getPort();
|
||||
this.updateServerBasePath = server.getBasePath();
|
||||
this.updateServerLocalPath = new File(server.getLocalPath());
|
||||
this.files = null;
|
||||
}
|
||||
|
||||
public UpdateServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public List<RequiredFile> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return updateServerName;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return updateServerAddress;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return updateServerPort;
|
||||
}
|
||||
|
||||
public String getBasePath() {
|
||||
return updateServerBasePath;
|
||||
}
|
||||
|
||||
public File getLocalPath() {
|
||||
return updateServerLocalPath;
|
||||
}
|
||||
|
||||
public void setFiles(List<RequiredFile> files) {
|
||||
this.files = files;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.services.data;
|
||||
|
||||
import com.projectswg.common.utilities.LocalUtilities;
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData;
|
||||
import com.projectswg.launcher.core.resources.data.announcements.AnnouncementsData;
|
||||
import com.projectswg.launcher.core.resources.data.announcements.CardData;
|
||||
import javafx.application.Platform;
|
||||
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
import me.joshlarson.json.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AnnouncementService extends Service {
|
||||
|
||||
private static final Map<String, String> VARIABLES = new HashMap<>();
|
||||
|
||||
static {
|
||||
VARIABLES.put("\\$\\{LAUNCHER.VERSION\\}", LauncherData.VERSION);
|
||||
}
|
||||
|
||||
private final ScheduledThreadPool executor;
|
||||
|
||||
public AnnouncementService() {
|
||||
this.executor = new ScheduledThreadPool(1, "announcement-service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
try (JSONInputStream jsonInputStream = new JSONInputStream(new FileInputStream(new File(LocalUtilities.getApplicationDirectory(), "announcements.json")))) {
|
||||
Map<String, Object> announcements = jsonInputStream.readObject();
|
||||
update(announcements);
|
||||
} catch (IOException | JSONException e) {
|
||||
Log.w("Failed to load announcements from disk");
|
||||
}
|
||||
|
||||
executor.start();
|
||||
executor.executeWithFixedDelay(3000, TimeUnit.MINUTES.toMillis(30), this::update);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
executor.stop();
|
||||
executor.awaitTermination(1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void update() {
|
||||
Map<String, Object> announcements = updateAnnouncements();
|
||||
if (announcements == null)
|
||||
return;
|
||||
update(announcements);
|
||||
}
|
||||
|
||||
private void update(Map<String, Object> announcements) {
|
||||
List<CardData> announcementCards = parseCards(announcements.get("announcements")).stream().map(this::downloadImage).collect(Collectors.toList());
|
||||
List<CardData> serverCards = parseCards(announcements.get("servers")).stream().map(this::downloadImage).collect(Collectors.toList());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
AnnouncementsData data = LauncherData.INSTANCE.getAnnouncements();
|
||||
data.getAnnouncementCards().clear();
|
||||
data.getAnnouncementCards().addAll(announcementCards);
|
||||
data.getServerListCards().clear();
|
||||
data.getServerListCards().addAll(serverCards);
|
||||
});
|
||||
}
|
||||
|
||||
private List<CardData> parseCards(Object descriptor) {
|
||||
if (!(descriptor instanceof List))
|
||||
return Collections.emptyList();
|
||||
|
||||
return ((List<?>) descriptor).stream().filter(JSONObject.class::isInstance).map(JSONObject.class::cast).filter(AnnouncementService::validateCard).map(this::parseCard).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private CardData parseCard(JSONObject obj) {
|
||||
String imageUrl = obj.getString("image");
|
||||
String title = obj.getString("title");
|
||||
String description = obj.getString("description");
|
||||
String link = obj.getString("link");
|
||||
title = title == null ? "" : parseVariables(title);
|
||||
description = description == null ? "" : parseVariables(description);
|
||||
|
||||
return new CardData(imageUrl, title, description, link);
|
||||
}
|
||||
|
||||
private CardData downloadImage(CardData card) {
|
||||
String url = card.getImageUrl();
|
||||
if (url == null)
|
||||
return card;
|
||||
int lastSlash = url.lastIndexOf('/');
|
||||
if (lastSlash == -1)
|
||||
return new CardData(null, card.getTitle(), card.getDescription(), card.getLink()); // Invalid url
|
||||
|
||||
File cards = new File(LocalUtilities.getApplicationDirectory(), "cards");
|
||||
if (!cards.isDirectory() && !cards.mkdir())
|
||||
Log.w("Could not create card directory");
|
||||
File destination = new File(cards, Integer.toHexString(url.hashCode()));
|
||||
download(url, destination);
|
||||
return new CardData(destination.getAbsolutePath(), card.getTitle(), card.getDescription(), card.getLink());
|
||||
}
|
||||
|
||||
private static void download(String url, File destination) {
|
||||
if (destination.isFile())
|
||||
return;
|
||||
Log.d("Downloading image '%s' to '%s'", url, destination);
|
||||
try {
|
||||
new URL(url).openStream().transferTo(new FileOutputStream(destination));
|
||||
Log.t("Completed download of %s", destination);
|
||||
} catch (IOException e) {
|
||||
Log.e("Failed to download file %s from %s with error: %s: %s", destination, url, e.getClass().getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean validateCard(JSONObject obj) {
|
||||
Map<String, Object> filter = obj.getObject("filter");
|
||||
if (filter == null)
|
||||
return true;
|
||||
String os = (String) filter.get("os");
|
||||
if (os != null) {
|
||||
String currentOs = System.getProperty("os.name").toLowerCase(Locale.US);
|
||||
switch (os.toLowerCase(Locale.US)) {
|
||||
case "windows":
|
||||
if (!currentOs.contains("win"))
|
||||
return false;
|
||||
break;
|
||||
case "mac":
|
||||
if (!currentOs.contains("mac"))
|
||||
return false;
|
||||
break;
|
||||
case "linux":
|
||||
if (currentOs.contains("win") || currentOs.contains("mac"))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Inclusive
|
||||
|
||||
return passesVersionCheck((String) filter.get("minLauncherVersion"), (cur, b) -> cur >= b, true) && passesVersionCheck((String) filter.get("maxLauncherVersion"), (cur, b) -> cur < b, false);
|
||||
}
|
||||
|
||||
private static String parseVariables(String str) {
|
||||
for (Entry<String, String> var : VARIABLES.entrySet()) {
|
||||
str = str.replaceAll(var.getKey(), var.getValue());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
private static boolean passesVersionCheck(String specifiedVersionStr, BiPredicate<Integer, Integer> check, boolean def) {
|
||||
if (specifiedVersionStr == null)
|
||||
return true;
|
||||
String [] currentVersion = LauncherData.VERSION.split("\\.");
|
||||
String [] specifiedVersion = specifiedVersionStr.split("\\.");
|
||||
for (int i = 0; i < currentVersion.length && i < specifiedVersion.length; i++) {
|
||||
int cur = Integer.parseUnsignedInt(currentVersion[i]);
|
||||
int spec = Integer.parseUnsignedInt(specifiedVersion[i]);
|
||||
if (cur == spec)
|
||||
continue;
|
||||
return check.test(cur, spec);
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage 1: Download the file list from the update server, or fall back on the local copy. If neither are accessible, fail.
|
||||
*/
|
||||
private static Map<String, Object> updateAnnouncements() {
|
||||
File localFileList = new File(LocalUtilities.getApplicationDirectory(), "announcements.json");
|
||||
|
||||
Log.t("Retrieving latest announcements...");
|
||||
Map<String, Object> announcements;
|
||||
try (JSONInputStream in = new JSONInputStream(new URL("http", LauncherData.UPDATE_ADDRESS, 80, "/launcher/announcements.json").openStream())) {
|
||||
announcements = in.readObject();
|
||||
try (JSONOutputStream out = new JSONOutputStream(new FileOutputStream(localFileList))) {
|
||||
out.writeObject(announcements);
|
||||
} catch (IOException e) {
|
||||
Log.e("Failed to write updated announcements to disk. %s: %s", e.getClass().getName(), e.getMessage());
|
||||
}
|
||||
} catch (IOException | JSONException e) {
|
||||
Log.w("Failed to retrieve latest announcements. Falling back on local copy...");
|
||||
try (JSONInputStream in = new JSONInputStream(new FileInputStream(localFileList))) {
|
||||
announcements = in.readObject();
|
||||
} catch (JSONException | IOException t) {
|
||||
Log.e("Failed to read announcements from disk. %s: %s", t.getClass().getName(), t.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return announcements;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.services.data;
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData;
|
||||
import com.projectswg.launcher.core.resources.data.forwarder.ForwarderData;
|
||||
import com.projectswg.launcher.core.resources.data.general.GeneralData;
|
||||
import com.projectswg.launcher.core.resources.data.general.LauncherTheme;
|
||||
import com.projectswg.launcher.core.resources.data.login.LoginData;
|
||||
import com.projectswg.launcher.core.resources.data.login.LoginServer;
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateData;
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer;
|
||||
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
public class PreferencesDataService extends Service {
|
||||
|
||||
private final Preferences preferences;
|
||||
private final ScheduledThreadPool executor;
|
||||
|
||||
public PreferencesDataService() {
|
||||
this.preferences = LauncherData.INSTANCE.getPreferences();
|
||||
this.executor = new ScheduledThreadPool(1, 3, "data-executor-%d");
|
||||
loadPreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
createDefaults();
|
||||
executor.start();
|
||||
executor.executeWithFixedDelay(5*60000, 5*60000, this::savePreferences);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
savePreferences();
|
||||
executor.stop();
|
||||
return executor.awaitTermination(1000);
|
||||
}
|
||||
|
||||
private void createDefaults() {
|
||||
createPSWG();
|
||||
createTeamSWG();
|
||||
|
||||
if (LauncherData.INSTANCE.getGeneral().getWine() == null || LauncherData.INSTANCE.getGeneral().getWine().isEmpty())
|
||||
LauncherData.INSTANCE.getGeneral().setWine(getWinePath());
|
||||
}
|
||||
|
||||
private void createPSWG() {
|
||||
UpdateServer pswgUpdateServer = LauncherData.INSTANCE.getUpdate().getServers().stream().filter(s -> s.getName().equals("ProjectSWG")).findFirst().orElse(null);
|
||||
if (pswgUpdateServer == null) {
|
||||
pswgUpdateServer = new UpdateServer("ProjectSWG");
|
||||
pswgUpdateServer.setAddress("login1.projectswg.com");
|
||||
pswgUpdateServer.setPort(80);
|
||||
pswgUpdateServer.setBasePath("/launcher/patch");
|
||||
pswgUpdateServer.setGameVersion("NGE");
|
||||
LauncherData.INSTANCE.getUpdate().addServer(pswgUpdateServer);
|
||||
} else if (pswgUpdateServer.getGameVersion().isEmpty()) {
|
||||
// Migrate existing persisted update servers
|
||||
pswgUpdateServer.setGameVersion("NGE");
|
||||
}
|
||||
if (LauncherData.INSTANCE.getLogin().getServers().stream().noneMatch(s -> s.getName().equals("ProjectSWG"))) {
|
||||
LoginServer defaultLive = new LoginServer("ProjectSWG");
|
||||
defaultLive.setAddress("login1.projectswg.com");
|
||||
defaultLive.setPort(44453);
|
||||
defaultLive.setUpdateServer(pswgUpdateServer);
|
||||
LauncherData.INSTANCE.getLogin().addServer(defaultLive);
|
||||
}
|
||||
|
||||
if (LauncherData.INSTANCE.getLogin().getServers().stream().noneMatch(s -> s.getName().equals("localhost"))) {
|
||||
LoginServer defaultLocalhost = new LoginServer("localhost");
|
||||
defaultLocalhost.setAddress("localhost");
|
||||
defaultLocalhost.setPort(44463);
|
||||
defaultLocalhost.setUpdateServer(pswgUpdateServer);
|
||||
LauncherData.INSTANCE.getLogin().addServer(defaultLocalhost);
|
||||
}
|
||||
}
|
||||
|
||||
private void createTeamSWG() {
|
||||
UpdateServer teamswgUpdateServer = LauncherData.INSTANCE.getUpdate().getServers().stream().filter(s -> s.getName().equals("TeamSWG")).findFirst().orElse(null);
|
||||
|
||||
if (teamswgUpdateServer == null) {
|
||||
teamswgUpdateServer = new UpdateServer("TeamSWG");
|
||||
teamswgUpdateServer.setAddress("patch.teamswg.com");
|
||||
teamswgUpdateServer.setPort(80);
|
||||
teamswgUpdateServer.setBasePath("/launcher/patch");
|
||||
teamswgUpdateServer.setGameVersion("CU");
|
||||
LauncherData.INSTANCE.getUpdate().addServer(teamswgUpdateServer);
|
||||
}
|
||||
|
||||
if (LauncherData.INSTANCE.getLogin().getServers().stream().noneMatch(s -> s.getName().equals("Constrictor"))) {
|
||||
LoginServer constrictor = new LoginServer("Constrictor");
|
||||
constrictor.setAddress("game.teamswg.com");
|
||||
constrictor.setPort(44463);
|
||||
constrictor.setUpdateServer(teamswgUpdateServer);
|
||||
LauncherData.INSTANCE.getLogin().addServer(constrictor);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void loadPreferences() {
|
||||
try {
|
||||
loadGeneralPreferences(LauncherData.INSTANCE.getGeneral());
|
||||
loadUpdatePreferences(LauncherData.INSTANCE.getUpdate());
|
||||
loadLoginPreferences(LauncherData.INSTANCE.getLogin());
|
||||
loadForwarderPreferences(LauncherData.INSTANCE.getForwarderData());
|
||||
} catch (BackingStoreException e) {
|
||||
Log.w(e);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void savePreferences() {
|
||||
try {
|
||||
saveGeneralPreferences(LauncherData.INSTANCE.getGeneral());
|
||||
saveUpdatePreferences(LauncherData.INSTANCE.getUpdate());
|
||||
saveLoginPreferences(LauncherData.INSTANCE.getLogin());
|
||||
saveForwarderPreferences(LauncherData.INSTANCE.getForwarderData());
|
||||
preferences.flush();
|
||||
} catch (BackingStoreException e) {
|
||||
Log.w(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadGeneralPreferences(GeneralData generalData) {
|
||||
Preferences generalPreferences = preferences.node("general");
|
||||
ifPresent(generalPreferences, "sound", Boolean::valueOf, generalData::setSound);
|
||||
ifPresent(generalPreferences, "theme", LauncherTheme::forThemeTag, generalData::setTheme);
|
||||
ifPresent(generalPreferences, "locale", Locale::forLanguageTag, generalData::setLocale);
|
||||
ifPresent(generalPreferences, "wine", generalData::setWine);
|
||||
ifPresent(generalPreferences, "admin", Boolean::valueOf, generalData::setAdmin);
|
||||
}
|
||||
|
||||
private void saveGeneralPreferences(GeneralData generalData) {
|
||||
Preferences generalPreferences = preferences.node("general");
|
||||
generalPreferences.putBoolean("sound", generalData.isSound());
|
||||
generalPreferences.put("theme", generalData.getTheme().getTag());
|
||||
generalPreferences.put("locale", generalData.getLocale().toLanguageTag());
|
||||
generalPreferences.putBoolean("admin", generalData.isAdmin());
|
||||
String wine = generalData.getWine();
|
||||
if (wine != null)
|
||||
generalPreferences.put("wine", wine);
|
||||
}
|
||||
|
||||
private void loadLoginPreferences(LoginData loginData) throws BackingStoreException {
|
||||
Preferences loginPreferences = preferences.node("login");
|
||||
for (String childNodeName : loginPreferences.childrenNames()) {
|
||||
Preferences loginServerPreferences = loginPreferences.node(childNodeName);
|
||||
LoginServer server = new LoginServer(childNodeName);
|
||||
ifPresent(loginServerPreferences, "address", server::setAddress);
|
||||
ifPresent(loginServerPreferences, "port", Integer::parseInt, server::setPort);
|
||||
ifPresent(loginServerPreferences, "username", server::setUsername);
|
||||
ifPresent(loginServerPreferences, "password", server::setPassword);
|
||||
ifPresent(loginServerPreferences, "updateServer", name -> server.setUpdateServer(LauncherData.INSTANCE.getUpdate().getServers().stream().filter(s -> s.getName().equals(name)).findFirst().orElse(null)));
|
||||
ifPresent(loginServerPreferences, "isVerifyServer", Boolean::parseBoolean, server::setVerifyServer);
|
||||
ifPresent(loginServerPreferences, "isEncryptionEnabled", Boolean::parseBoolean, server::setEncryptionEnabled);
|
||||
loginData.getServers().add(server);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveLoginPreferences(LoginData loginData) throws BackingStoreException {
|
||||
preferences.node("login").removeNode();
|
||||
Preferences loginPreferences = preferences.node("login");
|
||||
for (LoginServer server : loginData.getServers()) {
|
||||
Preferences loginServerPreferences = loginPreferences.node(server.getName());
|
||||
loginServerPreferences.put("address", server.getAddress());
|
||||
loginServerPreferences.putInt("port", server.getPort());
|
||||
loginServerPreferences.put("username", server.getUsername());
|
||||
loginServerPreferences.put("password", server.getPassword());
|
||||
loginServerPreferences.putBoolean("isVerifyServer", server.isVerifyServer());
|
||||
loginServerPreferences.putBoolean("isEncryptionEnabled", server.isEncryptionEnabled());
|
||||
UpdateServer updateServer = server.getUpdateServer();
|
||||
if (updateServer != null)
|
||||
loginServerPreferences.put("updateServer", updateServer.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadUpdatePreferences(UpdateData updateData) throws BackingStoreException {
|
||||
Preferences updatePreferences = preferences.node("update");
|
||||
for (String childNodeName : updatePreferences.childrenNames()) {
|
||||
Preferences updateServerPreferences = updatePreferences.node(childNodeName);
|
||||
UpdateServer server = new UpdateServer(childNodeName);
|
||||
ifPresent(updateServerPreferences, "address", server::setAddress);
|
||||
ifPresent(updateServerPreferences, "port", Integer::parseInt, server::setPort);
|
||||
ifPresent(updateServerPreferences, "basePath", server::setBasePath);
|
||||
ifPresent(updateServerPreferences, "localPath", server::setLocalPath);
|
||||
ifPresent(updateServerPreferences, "gameVersion", server::setGameVersion);
|
||||
updateData.getServers().add(server);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveUpdatePreferences(UpdateData updateData) throws BackingStoreException {
|
||||
preferences.node("update").removeNode();
|
||||
Preferences updatePreferences = preferences.node("update");
|
||||
for (UpdateServer server : updateData.getServers()) {
|
||||
Preferences updateServerPreferences = updatePreferences.node(server.getName());
|
||||
updateServerPreferences.put("address", server.getAddress());
|
||||
updateServerPreferences.putInt("port", server.getPort());
|
||||
updateServerPreferences.put("basePath", server.getBasePath());
|
||||
updateServerPreferences.put("localPath", server.getLocalPath());
|
||||
updateServerPreferences.put("gameVersion", server.getGameVersion());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadForwarderPreferences(ForwarderData forwarderData) {
|
||||
Preferences forwarderPreferences = preferences.node("forwarder");
|
||||
int version = forwarderPreferences.getInt("version", 0);
|
||||
if (version < 1) {
|
||||
forwarderData.setSendInterval(ForwarderData.DEFAULT_SEND_INTERVAL);
|
||||
forwarderData.setSendMax(ForwarderData.DEFAULT_SEND_MAX);
|
||||
} else {
|
||||
ifPresent(forwarderPreferences, "sendInterval", Integer::valueOf, forwarderData::setSendInterval);
|
||||
ifPresent(forwarderPreferences, "sendMax", Integer::valueOf, forwarderData::setSendMax);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveForwarderPreferences(ForwarderData forwarderData) {
|
||||
Preferences forwarderPreferences = preferences.node("forwarder");
|
||||
forwarderPreferences.putInt("sendInterval", forwarderData.getSendInterval());
|
||||
forwarderPreferences.putInt("sendMax", forwarderData.getSendMax());
|
||||
forwarderPreferences.putInt("version", 1);
|
||||
}
|
||||
|
||||
private static <T> void ifPresent(Preferences p, String key, Function<String, T> transform, Consumer<T> setter) {
|
||||
String val = p.get(key, null);
|
||||
if (val != null)
|
||||
setter.accept(transform.apply(val));
|
||||
}
|
||||
|
||||
private static void ifPresent(Preferences p, String key, Consumer<String> setter) {
|
||||
String val = p.get(key, null);
|
||||
if (val != null)
|
||||
setter.accept(val);
|
||||
}
|
||||
|
||||
private static String getWinePath() {
|
||||
String pathStr = System.getenv("PATH");
|
||||
if (pathStr == null)
|
||||
return null;
|
||||
|
||||
for (String path : pathStr.split(File.pathSeparator)) {
|
||||
Log.t("Testing wine binary at %s", path);
|
||||
File test = new File(path, "wine");
|
||||
if (test.isFile()) {
|
||||
try {
|
||||
test = test.getCanonicalFile();
|
||||
Log.d("Found wine installation. Location: %s", test);
|
||||
return test.getAbsolutePath();
|
||||
} catch (IOException e) {
|
||||
Log.w("Failed to get canonical file location of possible wine location: %s", test);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.services.data
|
||||
|
||||
import com.projectswg.holocore.client.HolocoreSocket
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.data.login.LoginData
|
||||
import com.projectswg.launcher.core.resources.data.login.LoginServer
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateData
|
||||
import com.projectswg.launcher.core.resources.intents.RequestScanIntent
|
||||
import com.projectswg.launcher.core.resources.pipeline.UpdateServerUpdater
|
||||
import javafx.beans.InvalidationListener
|
||||
import me.joshlarson.jlcommon.collections.TransferSet
|
||||
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool
|
||||
import me.joshlarson.jlcommon.control.IntentHandler
|
||||
import me.joshlarson.jlcommon.control.Service
|
||||
import me.joshlarson.jlcommon.javafx.beans.ConcurrentCollection
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import tornadofx.FX
|
||||
import tornadofx.get
|
||||
import java.net.InetAddress
|
||||
import java.net.UnknownHostException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class RemoteDataService : Service() {
|
||||
|
||||
private val loginServers: TransferSet<LoginServer, LoginServerUpdater>
|
||||
private val executor: ScheduledThreadPool
|
||||
|
||||
init {
|
||||
this.loginServers = TransferSet( { it.name }, { LoginServerUpdater(it) })
|
||||
this.executor = ScheduledThreadPool(2, "remote-data-service")
|
||||
|
||||
loginServers.addDestroyCallback { it.terminate() }
|
||||
loginData.servers.addCollectionChangedListener(LISTENER_KEY, ConcurrentCollection.ComplexCollectionChangedListener<ConcurrentCollection<Set<LoginServer>, LoginServer>> { loginServers.synchronize(it) })
|
||||
}
|
||||
|
||||
override fun initialize(): Boolean {
|
||||
loginServers.synchronize(loginData.servers)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun start(): Boolean {
|
||||
executor.start()
|
||||
// Updates the status of the login server (OFFLINE/LOADING/UP/LOCKED)
|
||||
executor.executeWithFixedDelay(0, TimeUnit.SECONDS.toMillis(10)) { this.updateLoginServers() }
|
||||
// Retrieves the latest file list for each update server
|
||||
executor.executeWithFixedDelay(0, TimeUnit.MINUTES.toMillis(30)) { this.updateUpdateServers() }
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stop(): Boolean {
|
||||
executor.stop()
|
||||
return executor.awaitTermination(1000)
|
||||
}
|
||||
|
||||
override fun terminate(): Boolean {
|
||||
loginServers.synchronize(emptyList())
|
||||
return true
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private fun handleRequestScanIntent(rsi: RequestScanIntent) {
|
||||
UpdateServerUpdater.update(rsi.server)
|
||||
}
|
||||
|
||||
private fun updateLoginServers() {
|
||||
// Allows for parallel networking operations
|
||||
loginServers.parallelStream().forEach { it.update() }
|
||||
}
|
||||
|
||||
private fun updateUpdateServers() {
|
||||
updateData.servers.parallelStream().forEach { UpdateServerUpdater.update(it) }
|
||||
}
|
||||
|
||||
private class LoginServerUpdater(private val server: LoginServer) {
|
||||
private var socket: HolocoreSocket? = null
|
||||
|
||||
init {
|
||||
this.socket = null
|
||||
|
||||
server.addressProperty.addListener(InvalidationListener { updateSocket() })
|
||||
server.portProperty.addListener(InvalidationListener { updateSocket() })
|
||||
updateSocket()
|
||||
}
|
||||
|
||||
fun terminate() {
|
||||
socket?.close()
|
||||
}
|
||||
|
||||
fun update() {
|
||||
// Better luck next time
|
||||
val socket = this.socket
|
||||
if (socket == null) {
|
||||
server.instanceInfo.loginStatus = ""
|
||||
return
|
||||
}
|
||||
if (server.instanceInfo.loginStatus.isNullOrBlank())
|
||||
server.instanceInfo.loginStatus = FX.messages["servers.loginStatus.checking"]
|
||||
|
||||
for (i in 0..4) {
|
||||
val status = socket.getServerStatus(1000)
|
||||
if (status != "OFFLINE") {
|
||||
server.instanceInfo.loginStatus = status
|
||||
return
|
||||
}
|
||||
}
|
||||
server.instanceInfo.loginStatus = "OFFLINE"
|
||||
}
|
||||
|
||||
private fun updateSocket() {
|
||||
try {
|
||||
val addr = server.address.trim { it <= ' ' }
|
||||
val port = server.port
|
||||
if (addr.isEmpty() || port <= 0)
|
||||
return
|
||||
this.socket = HolocoreSocket(InetAddress.getByName(addr), port)
|
||||
} catch (e: UnknownHostException) {
|
||||
this.socket = null
|
||||
Log.w(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val LISTENER_KEY = "RDS"
|
||||
|
||||
private val loginData: LoginData
|
||||
get() = LauncherData.INSTANCE.login
|
||||
|
||||
private val updateData: UpdateData
|
||||
get() = LauncherData.INSTANCE.update
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
package com.projectswg.launcher.resources.data
|
||||
|
||||
import com.projectswg.launcher.resources.data.forwarder.ForwarderData
|
||||
import com.projectswg.launcher.resources.data.general.GeneralData
|
||||
import com.projectswg.launcher.resources.data.login.LoginData
|
||||
import com.projectswg.launcher.resources.data.update.UpdateData
|
||||
import javafx.application.Application
|
||||
import javafx.stage.Stage
|
||||
import tornadofx.FX
|
||||
import tornadofx.FX.Companion.primaryStage
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
enum class LauncherData {
|
||||
INSTANCE;
|
||||
|
||||
val general = GeneralData()
|
||||
val login = LoginData()
|
||||
val update = UpdateData()
|
||||
val forwarder = ForwarderData()
|
||||
val stage: Stage
|
||||
get() = primaryStage
|
||||
|
||||
val application: Application
|
||||
get() = FX.application
|
||||
|
||||
companion object {
|
||||
const val VERSION = "2.0.0"
|
||||
|
||||
fun getApplicationDataDirectory(): File {
|
||||
return when (getOS()) {
|
||||
"windows" -> File(System.getenv("APPDATA"), "ProjectSWG")
|
||||
"mac" -> File("${System.getProperty("user.home")}/Library/Application Support/ProjectSWG")
|
||||
else -> File("${System.getProperty("user.home")}/.config/ProjectSWG")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOS(): String {
|
||||
val currentOs = System.getProperty("os.name").lowercase(Locale.US)
|
||||
return when {
|
||||
currentOs.contains("win") -> "windows"
|
||||
currentOs.contains("mac") -> "mac"
|
||||
else -> "linux"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.announcements
|
||||
package com.projectswg.launcher.resources.data.announcements
|
||||
|
||||
import com.rometools.rome.feed.synd.SyndFeed
|
||||
import com.rometools.rome.io.SyndFeedInput
|
||||
@@ -43,12 +43,12 @@ class WebsitePostFeed(feed: SyndFeed) {
|
||||
date = it.publishedDate,
|
||||
link = it.link,
|
||||
description = StringEscapeUtils.unescapeXml(it.description.value),
|
||||
image = when(it.categories.getOrNull(0)?.name?.toLowerCase(Locale.US)) {
|
||||
image = when(it.categories.getOrNull(0)?.name?.lowercase(Locale.US)) {
|
||||
"development update", "quality assurance" -> WebsitePostMessageImage.DEVELOPMENT
|
||||
"community update" -> WebsitePostMessageImage.COMMUNITY
|
||||
"holocore update" -> WebsitePostMessageImage.HOLOCORE
|
||||
else -> {
|
||||
Log.w("Unknown RSS category type: '%s'", it.categories.getOrNull(0)?.name?.toLowerCase(Locale.US))
|
||||
Log.w("Unknown RSS category type: '%s'", it.categories.getOrNull(0)?.name?.lowercase(Locale.US))
|
||||
WebsitePostMessageImage.OPERATIONS
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.projectswg.launcher.core.resources.data.announcements
|
||||
package com.projectswg.launcher.resources.data.announcements
|
||||
|
||||
import java.util.Date
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.announcements
|
||||
package com.projectswg.launcher.resources.data.announcements
|
||||
|
||||
enum class WebsitePostMessageImage(val descriptionStr: String, val imagePath: String) {
|
||||
COMMUNITY("Community Update", "/graphics/pswg_icon_big.png"),
|
||||
@@ -18,7 +18,7 @@
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.forwarder
|
||||
package com.projectswg.launcher.resources.data.forwarder
|
||||
|
||||
import javafx.beans.property.SimpleIntegerProperty
|
||||
import tornadofx.getValue
|
||||
@@ -17,23 +17,25 @@
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
package com.projectswg.launcher.resources.data.general
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.announcements
|
||||
import javafx.beans.property.SimpleBooleanProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import java.util.*
|
||||
import tornadofx.getValue
|
||||
import tornadofx.setValue
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
||||
import me.joshlarson.jlcommon.javafx.beans.ConcurrentList
|
||||
|
||||
class AnnouncementsData {
|
||||
class GeneralData {
|
||||
|
||||
val announcementCards = ConcurrentList<CardData>()
|
||||
val announcementCardsProperty: ObservableList<CardData> = FXCollections.observableArrayList()
|
||||
val serverListCards = ConcurrentList<CardData>()
|
||||
val serverListCardsProperty: ObservableList<CardData> = FXCollections.observableArrayList()
|
||||
val localeProperty = SimpleObjectProperty(Locale.getDefault())
|
||||
val wineProperty = SimpleStringProperty()
|
||||
val adminProperty = SimpleBooleanProperty(false)
|
||||
val remoteVersionProperty = SimpleStringProperty("")
|
||||
|
||||
init {
|
||||
announcementCards.addCollectionChangedListener("", Runnable { announcementCardsProperty.setAll(announcementCards) })
|
||||
serverListCards.addCollectionChangedListener("", Runnable { serverListCardsProperty.setAll(serverListCards) })
|
||||
}
|
||||
var locale: Locale by localeProperty
|
||||
var wine: String? by wineProperty
|
||||
var isAdmin: Boolean by adminProperty
|
||||
var remoteVersion: String by remoteVersionProperty
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.projectswg.launcher.resources.data.login
|
||||
|
||||
import javafx.beans.property.ReadOnlyStringWrapper
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import tornadofx.getValue
|
||||
import tornadofx.setValue
|
||||
|
||||
class AuthenticationData(val name: String) {
|
||||
|
||||
val nameProperty = ReadOnlyStringWrapper(name)
|
||||
val usernameProperty = SimpleStringProperty("")
|
||||
val passwordProperty = SimpleStringProperty("")
|
||||
|
||||
var username: String by usernameProperty
|
||||
var password: String by passwordProperty
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.resources.data.login
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
||||
import me.joshlarson.jlcommon.javafx.beans.ConcurrentSet
|
||||
import tornadofx.getValue
|
||||
import tornadofx.select
|
||||
import tornadofx.setValue
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
class LoginData {
|
||||
|
||||
val authenticationData = ConcurrentSet<AuthenticationData>(CopyOnWriteArraySet())
|
||||
val authenticationProperty: ObservableList<AuthenticationData> = FXCollections.observableArrayList()
|
||||
|
||||
val servers = ConcurrentSet<LoginServer>(CopyOnWriteArraySet())
|
||||
val serversProperty: ObservableList<LoginServer> = FXCollections.observableArrayList()
|
||||
|
||||
val activeServerProperty = SimpleObjectProperty<LoginServer>()
|
||||
val lastSelectedServerProperty = SimpleStringProperty()
|
||||
|
||||
val localServerProperty = SimpleObjectProperty<LoginServer>()
|
||||
|
||||
var activeServer: LoginServer by activeServerProperty
|
||||
val lastSelectedServer: String by lastSelectedServerProperty
|
||||
|
||||
var localServer: LoginServer by localServerProperty
|
||||
|
||||
init {
|
||||
servers.addCollectionChangedListener("", Runnable { serversProperty.setAll(servers) })
|
||||
authenticationData.addCollectionChangedListener("", Runnable { authenticationProperty.setAll(authenticationData) })
|
||||
lastSelectedServerProperty.bind(activeServerProperty.select { it.nameProperty })
|
||||
}
|
||||
|
||||
fun getAuthenticationData(authentication_name: String): AuthenticationData? {
|
||||
for (authentication in authenticationData) {
|
||||
if (authentication.name == authentication_name)
|
||||
return authentication
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun getServerByName(serverName: String): LoginServer? {
|
||||
for (server in servers) {
|
||||
if (server.name == serverName)
|
||||
return server
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,10 +18,10 @@
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.login
|
||||
package com.projectswg.launcher.resources.data.login
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer.UpdateServerStatus
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer.UpdateServerStatus
|
||||
import javafx.beans.property.*
|
||||
import javafx.beans.value.ObservableValue
|
||||
import tornadofx.getValue
|
||||
@@ -30,24 +30,16 @@ import tornadofx.setValue
|
||||
class LoginServer(val name: String) {
|
||||
|
||||
val nameProperty = ReadOnlyStringWrapper(name)
|
||||
val addressProperty = SimpleStringProperty("")
|
||||
val portProperty = SimpleIntegerProperty(0)
|
||||
val usernameProperty = SimpleStringProperty("")
|
||||
val passwordProperty = SimpleStringProperty("")
|
||||
val connectionUriProperty = SimpleStringProperty("")
|
||||
val authenticationProperty = SimpleObjectProperty<AuthenticationData>(null)
|
||||
val updateServerProperty = SimpleObjectProperty<UpdateServer>(null)
|
||||
val verifyServerProperty = SimpleBooleanProperty(true)
|
||||
val enableEncryptionProperty = SimpleBooleanProperty(true)
|
||||
val instanceInfo = LoginServerInstanceInfo()
|
||||
|
||||
private val updateServerStatusCallback= { _: ObservableValue<*>, _: UpdateServerStatus, status: UpdateServerStatus -> onUpdateServerStatusUpdated(status) }
|
||||
|
||||
var address: String by addressProperty
|
||||
var port: Int by portProperty
|
||||
var username: String by usernameProperty
|
||||
var password: String by passwordProperty
|
||||
var connectionUri: String by connectionUriProperty
|
||||
var authentication: AuthenticationData by authenticationProperty
|
||||
var updateServer: UpdateServer? by updateServerProperty
|
||||
var isVerifyServer: Boolean by verifyServerProperty
|
||||
var isEncryptionEnabled: Boolean by enableEncryptionProperty
|
||||
|
||||
init {
|
||||
updateServerProperty.addListener { _, oldValue, newValue -> updateServerListener(oldValue, newValue) }
|
||||
@@ -1,38 +1,37 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
package com.projectswg.launcher.resources.data.login
|
||||
|
||||
package com.projectswg.launcher.core.services.data;
|
||||
import javafx.beans.property.SimpleBooleanProperty
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import tornadofx.getValue
|
||||
import tornadofx.setValue
|
||||
|
||||
import me.joshlarson.jlcommon.control.Manager;
|
||||
import me.joshlarson.jlcommon.control.ManagerStructure;
|
||||
|
||||
@ManagerStructure(children = {
|
||||
PreferencesDataService.class,
|
||||
RemoteDataService.class,
|
||||
DownloadService.class,
|
||||
AnnouncementService.class
|
||||
})
|
||||
public class DataManager extends Manager {
|
||||
class LoginServerInstanceInfo {
|
||||
|
||||
public DataManager() {
|
||||
|
||||
}
|
||||
val loginStatusProperty = SimpleStringProperty("")
|
||||
val updateStatusProperty = SimpleStringProperty("")
|
||||
val readyToPlayProperty = SimpleBooleanProperty(false)
|
||||
|
||||
}
|
||||
var loginStatus: String by loginStatusProperty
|
||||
var updateStatus: String by updateStatusProperty
|
||||
var isReadyToPlay: Boolean by readyToPlayProperty
|
||||
|
||||
}
|
||||
@@ -17,30 +17,35 @@
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
package com.projectswg.launcher.resources.data.update
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.login
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
||||
import me.joshlarson.jlcommon.javafx.beans.ConcurrentSet
|
||||
|
||||
import tornadofx.getValue
|
||||
import tornadofx.setValue
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
class LoginData {
|
||||
class UpdateData {
|
||||
|
||||
val servers = ConcurrentSet<LoginServer>(CopyOnWriteArraySet())
|
||||
val serversProperty: ObservableList<LoginServer> = FXCollections.observableArrayList()
|
||||
val servers: ConcurrentSet<UpdateServer> = ConcurrentSet(CopyOnWriteArraySet())
|
||||
val serversProperty: ObservableList<UpdateServer> = FXCollections.observableArrayList()
|
||||
|
||||
val localPathProperty = SimpleStringProperty("")
|
||||
var localPath: String by localPathProperty
|
||||
|
||||
init {
|
||||
servers.addCollectionChangedListener("", Runnable { serversProperty.setAll(servers) })
|
||||
}
|
||||
|
||||
fun addServer(server: LoginServer) {
|
||||
servers.add(server)
|
||||
fun getServer(server_name: String): UpdateServer? {
|
||||
for (server in servers) {
|
||||
if (server.name == server_name)
|
||||
return server
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun removeServer(server: LoginServer) {
|
||||
servers.remove(server)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,10 @@
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.data.update
|
||||
package com.projectswg.launcher.resources.data.update
|
||||
|
||||
import com.projectswg.launcher.resources.data.LauncherData
|
||||
import javafx.beans.property.SimpleDoubleProperty
|
||||
import javafx.beans.property.SimpleIntegerProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.collections.FXCollections
|
||||
@@ -33,19 +33,20 @@ import java.net.URL
|
||||
|
||||
class UpdateServer(val name: String) {
|
||||
|
||||
val addressProperty = SimpleStringProperty("")
|
||||
val portProperty = SimpleIntegerProperty(0)
|
||||
val basePathProperty = SimpleStringProperty("")
|
||||
val localPathProperty = SimpleStringProperty("")
|
||||
val friendlyNameProperty = SimpleStringProperty("")
|
||||
val urlProperty = SimpleStringProperty("")
|
||||
val statusProperty = SimpleObjectProperty(UpdateServerStatus.UNKNOWN)
|
||||
val gameVersionProperty = SimpleStringProperty("")
|
||||
val requiredFiles: ObservableList<RequiredFile> = FXCollections.observableArrayList()
|
||||
val downloadProgressProperty = SimpleDoubleProperty(-1.0)
|
||||
|
||||
var address: String by addressProperty
|
||||
var port by portProperty
|
||||
var basePath: String by basePathProperty
|
||||
var localPath: String by localPathProperty
|
||||
val localPath: File
|
||||
get() {
|
||||
return File(LauncherData.INSTANCE.update.localPath, gameVersion)
|
||||
}
|
||||
|
||||
var friendlyName: String by friendlyNameProperty
|
||||
var url: String by urlProperty
|
||||
var status: UpdateServerStatus by statusProperty
|
||||
var gameVersion: String by gameVersionProperty
|
||||
var downloadProgress by downloadProgressProperty
|
||||
@@ -54,7 +55,7 @@ class UpdateServer(val name: String) {
|
||||
return name
|
||||
}
|
||||
|
||||
class RequiredFile(val localPath: File, val remotePath: URL, val length: Long, val hash: Long)
|
||||
class RequiredFile(val localPath: File, val remotePath: URL, val length: Long, val hash: String)
|
||||
|
||||
enum class UpdateServerStatus constructor(val friendlyName: String) {
|
||||
UNKNOWN("servers.status.unknown"),
|
||||
@@ -18,12 +18,11 @@
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.game
|
||||
package com.projectswg.launcher.resources.game
|
||||
|
||||
import com.projectswg.forwarder.Forwarder
|
||||
import com.projectswg.forwarder.Forwarder.ForwarderData
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.data.login.LoginServer
|
||||
import com.projectswg.launcher.resources.data.login.LoginServer
|
||||
import javafx.application.Platform
|
||||
import javafx.scene.control.Alert
|
||||
import javafx.scene.control.Alert.AlertType
|
||||
@@ -31,7 +30,6 @@ import me.joshlarson.jlcommon.concurrency.BasicThread
|
||||
import me.joshlarson.jlcommon.concurrency.Delay
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import java.io.File
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
@@ -44,19 +42,15 @@ class GameInstance(private val server: LoginServer) {
|
||||
|
||||
init {
|
||||
val gameId = GAME_ID.incrementAndGet()
|
||||
this.processThread = BasicThread("game-process-$gameId", Runnable { this.runProcess() })
|
||||
this.forwarderThread = BasicThread("game-forwarder-$gameId", Runnable { this.runForwarder() })
|
||||
this.processThread = BasicThread("game-process-$gameId") { this.runProcess() }
|
||||
this.forwarderThread = BasicThread("game-forwarder-$gameId") { this.runForwarder() }
|
||||
}
|
||||
|
||||
fun start() {
|
||||
val data = forwarder.data
|
||||
data.address = InetSocketAddress(server.address, server.port)
|
||||
data.isVerifyServer = server.isVerifyServer
|
||||
data.isEncryptionEnabled = server.isEncryptionEnabled
|
||||
data.username = server.username
|
||||
data.password = server.password
|
||||
data.outboundTunerInterval = LauncherData.INSTANCE.forwarderData.sendInterval
|
||||
data.outboundTunerMaxSend = LauncherData.INSTANCE.forwarderData.sendMax
|
||||
data.baseConnectionUri = server.connectionUri
|
||||
data.outboundTunerInterval = com.projectswg.launcher.resources.data.LauncherData.INSTANCE.forwarder.sendInterval
|
||||
data.outboundTunerMaxSend = com.projectswg.launcher.resources.data.LauncherData.INSTANCE.forwarder.sendMax
|
||||
forwarderThread.start()
|
||||
}
|
||||
|
||||
@@ -101,7 +95,7 @@ class GameInstance(private val server: LoginServer) {
|
||||
|
||||
private fun onCrash(crashLog: File) {
|
||||
Log.w("Crash Detected. ZIP: %s", crashLog)
|
||||
reportWarning("Crash Detected", "A crash was detected. Please report this to the ProjectSWG team with this zip file: $crashLog")
|
||||
reportCrash("A crash was detected. Please report this to the ProjectSWG team with this zip file: $crashLog")
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -131,7 +125,7 @@ class GameInstance(private val server: LoginServer) {
|
||||
reportError("Process", "No update server defined")
|
||||
return null
|
||||
}
|
||||
val swgDirectory = File(updateServer.localPath)
|
||||
val swgDirectory = updateServer.localPath
|
||||
if (!swgDirectory.isDirectory) {
|
||||
Log.e("Failed to launch game. Invalid SWG directory: %s", swgDirectory)
|
||||
reportError("Process", "Invalid SWG directory: $swgDirectory")
|
||||
@@ -152,7 +146,7 @@ class GameInstance(private val server: LoginServer) {
|
||||
"autoConnectToLoginServer=" + username.isNotEmpty(),
|
||||
"logReportFatals=true",
|
||||
"logStderr=true",
|
||||
"0fd345d9=" + if (LauncherData.INSTANCE.general.isAdmin) "true" else "false",
|
||||
"0fd345d9=" + if (com.projectswg.launcher.resources.data.LauncherData.INSTANCE.general.isAdmin) "true" else "false",
|
||||
"-s",
|
||||
"SharedNetwork",
|
||||
"useTcp=false",
|
||||
@@ -165,11 +159,11 @@ class GameInstance(private val server: LoginServer) {
|
||||
"networkHandlerDispatchQueueSize=2000")
|
||||
}
|
||||
|
||||
private fun reportWarning(title: String, message: String) {
|
||||
private fun reportCrash(message: String) {
|
||||
Platform.runLater {
|
||||
val alert = Alert(AlertType.WARNING)
|
||||
alert.title = "Game Launch Warning"
|
||||
alert.headerText = title
|
||||
alert.headerText = "Crash Detected"
|
||||
alert.contentText = message
|
||||
alert.showAndWait()
|
||||
}
|
||||
@@ -18,10 +18,10 @@
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.game;
|
||||
package com.projectswg.launcher.resources.game;
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData;
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer;
|
||||
import com.projectswg.launcher.resources.data.LauncherData;
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
@@ -40,7 +40,7 @@ public enum ProcessExecutor {
|
||||
public Process buildProcess(@NotNull UpdateServer server, String executable, String ... args) {
|
||||
File swgDirectory;
|
||||
{
|
||||
swgDirectory = new File(server.getLocalPath());
|
||||
swgDirectory = server.getLocalPath();
|
||||
if (!swgDirectory.isDirectory()) {
|
||||
Log.e("Failed to launch. Invalid SWG directory: %s", swgDirectory);
|
||||
reportError("Process", "Invalid SWG directory: " + swgDirectory);
|
||||
@@ -120,7 +120,7 @@ public enum ProcessExecutor {
|
||||
return new File(wineStr);
|
||||
} else {
|
||||
Log.e("Invalid wine file: " + wineStr);
|
||||
reportWarning("Wine Initialization", "Invalid wine setting. Searching for valid path...");
|
||||
reportInvalidWinePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,12 +145,12 @@ public enum ProcessExecutor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void reportWarning(String title, String message) {
|
||||
private void reportInvalidWinePath() {
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(AlertType.WARNING);
|
||||
alert.setTitle("Game Launch Warning");
|
||||
alert.setHeaderText(title);
|
||||
alert.setContentText(message);
|
||||
alert.setHeaderText("Wine Initialization");
|
||||
alert.setContentText("Invalid wine setting. Searching for valid path...");
|
||||
alert.showAndWait();
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.projectswg.launcher.core.resources.gui
|
||||
package com.projectswg.launcher.resources.gui
|
||||
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
|
||||
@@ -18,27 +18,24 @@
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui
|
||||
package com.projectswg.launcher.resources.gui
|
||||
|
||||
import com.projectswg.launcher.resources.gui.style.Style
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import javafx.beans.property.Property
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.geometry.Side
|
||||
import javafx.scene.layout.AnchorPane
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.text.Font
|
||||
import tornadofx.*
|
||||
|
||||
class NavigationView : View("ProjectSWG Launcher") {
|
||||
|
||||
override val root = anchorpane {
|
||||
stylesheets += "/css/theme.css"
|
||||
addClass(Style.background)
|
||||
|
||||
var selectedTab: Property<String> = SimpleStringProperty("")
|
||||
tabpane {
|
||||
prefWidth = 825.0
|
||||
prefHeight = 640.0
|
||||
prefHeight = 400.0
|
||||
vgrow = Priority.ALWAYS
|
||||
side = Side.LEFT
|
||||
|
||||
@@ -47,10 +44,7 @@ class NavigationView : View("ProjectSWG Launcher") {
|
||||
AnchorPane.setBottomAnchor(this, 0.0)
|
||||
AnchorPane.setLeftAnchor(this, 0.0)
|
||||
|
||||
selectedTab = selectionModel.selectedItemProperty().select { it.textProperty() }
|
||||
|
||||
tab(messages["servers"]) {
|
||||
styleClass += "background"
|
||||
isClosable = false
|
||||
graphic = FontAwesomeIcon.SERVER.createGlyph(glyphSize = 24, fill = Color.LIGHTGRAY)
|
||||
graphic.tooltip(messages["servers"])
|
||||
@@ -59,7 +53,6 @@ class NavigationView : View("ProjectSWG Launcher") {
|
||||
selectionModel.select(this)
|
||||
}
|
||||
tab(messages["settings"]) {
|
||||
styleClass += "background"
|
||||
isClosable = false
|
||||
graphic = FontAwesomeIcon.SLIDERS.createGlyph(glyphSize = 24, fill = Color.LIGHTGRAY)
|
||||
graphic.tooltip(messages["settings"])
|
||||
@@ -67,16 +60,6 @@ class NavigationView : View("ProjectSWG Launcher") {
|
||||
this += find<SettingsView>().root
|
||||
}
|
||||
}
|
||||
|
||||
group {
|
||||
AnchorPane.setBottomAnchor(this, 15.0)
|
||||
AnchorPane.setLeftAnchor(this, 5.0)
|
||||
|
||||
label(selectedTab) {
|
||||
rotate = -90.0
|
||||
font = Font(28.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2020 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.resources.gui
|
||||
|
||||
import com.projectswg.launcher.resources.data.LauncherData
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.resources.game.GameInstance
|
||||
import com.projectswg.launcher.resources.game.ProcessExecutor
|
||||
import com.projectswg.launcher.resources.gui.events.LauncherNewVersionEvent
|
||||
import com.projectswg.launcher.resources.gui.servers.LauncherUpdatePopup
|
||||
import com.projectswg.launcher.resources.gui.servers.WebsitePostFeedList
|
||||
import com.projectswg.launcher.resources.gui.style.Style
|
||||
import com.projectswg.launcher.resources.intents.DownloadPatchIntent
|
||||
import com.projectswg.launcher.resources.intents.GameLaunchedIntent
|
||||
import com.projectswg.launcher.resources.intents.RequestScanIntent
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper
|
||||
import javafx.beans.property.ReadOnlyStringWrapper
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.text.FontWeight
|
||||
import javafx.stage.StageStyle
|
||||
import tornadofx.*
|
||||
|
||||
class ServerListView : View() {
|
||||
|
||||
private val feedList: WebsitePostFeedList by inject()
|
||||
|
||||
override val root = vbox {
|
||||
subscribe<LauncherNewVersionEvent>(times = 1) {
|
||||
find<LauncherUpdatePopup>().openModal(stageStyle = StageStyle.UNDECORATED)
|
||||
}
|
||||
|
||||
addClass(Style.serverList)
|
||||
|
||||
hbox {
|
||||
isFillWidth = true
|
||||
|
||||
vbox leftBox@ {
|
||||
prefWidthProperty().bind(this@hbox.widthProperty().subtract(5).divide(2))
|
||||
|
||||
hbox {
|
||||
label(messages["servers.login.feed.title"]) {
|
||||
addClass(Style.settingsHeaderLabel)
|
||||
style {
|
||||
padding = box(20.px)
|
||||
}
|
||||
}
|
||||
|
||||
style {
|
||||
backgroundColor += Style.backgroundColorSecondary
|
||||
}
|
||||
}
|
||||
|
||||
this += feedList.root
|
||||
}
|
||||
region {
|
||||
prefWidth = 5.0
|
||||
}
|
||||
vbox rightBox@ {
|
||||
prefWidthProperty().bind(this@hbox.widthProperty().subtract(5).divide(2))
|
||||
|
||||
hbox {
|
||||
label(messages["servers.login.form.title"]) {
|
||||
addClass(Style.settingsHeaderLabel)
|
||||
style {
|
||||
padding = box(20.px)
|
||||
}
|
||||
}
|
||||
|
||||
style {
|
||||
backgroundColor += Style.backgroundColorSecondary
|
||||
}
|
||||
}
|
||||
|
||||
// Login container
|
||||
form {
|
||||
fieldset {
|
||||
field("Server:") {
|
||||
combobox(LauncherData.INSTANCE.login.activeServerProperty) {
|
||||
items = LauncherData.INSTANCE.login.serversProperty
|
||||
valueProperty().bindBidirectional(LauncherData.INSTANCE.login.activeServerProperty)
|
||||
prefWidth = 300.0
|
||||
}
|
||||
}
|
||||
|
||||
field(messages["servers.login.form.username"]) {
|
||||
textfield(LauncherData.INSTANCE.login.activeServerProperty.select { it.authenticationProperty }.select { it.usernameProperty })
|
||||
}
|
||||
field(messages["servers.login.form.password"]) {
|
||||
passwordfield(LauncherData.INSTANCE.login.activeServerProperty.select { it.authenticationProperty }.select { it.passwordProperty })
|
||||
}
|
||||
field(messages["servers.column.localStatus"]) {
|
||||
val updateStatus = LauncherData.INSTANCE.login.activeServerProperty.select { it.updateServerProperty }.select { it.statusProperty }
|
||||
|
||||
label(observable=LauncherData.INSTANCE.login.activeServerProperty.select { it.instanceInfo.updateStatusProperty }.select { ReadOnlyStringWrapper(messages[it]) }) {
|
||||
maxWidth = Double.POSITIVE_INFINITY
|
||||
isFillWidth = true
|
||||
|
||||
textFillProperty().bind(updateStatus.select { when (it) {
|
||||
UpdateServer.UpdateServerStatus.READY -> ReadOnlyObjectWrapper(Color.rgb(0, 255, 0))
|
||||
UpdateServer.UpdateServerStatus.REQUIRES_DOWNLOAD -> ReadOnlyObjectWrapper(Color.RED)
|
||||
else -> ReadOnlyObjectWrapper(Color.WHITE)
|
||||
} })
|
||||
|
||||
style {
|
||||
fontWeight = FontWeight.BOLD
|
||||
}
|
||||
}
|
||||
button("Patch") {
|
||||
val targetWidthProperty = updateStatus.select { ReadOnlyDoubleWrapper(if (it == UpdateServer.UpdateServerStatus.REQUIRES_DOWNLOAD) 75.0 else 0.0) }
|
||||
visibleWhen { updateStatus.select { ReadOnlyBooleanWrapper(it == UpdateServer.UpdateServerStatus.REQUIRES_DOWNLOAD) } }
|
||||
minWidthProperty().bind(targetWidthProperty)
|
||||
maxWidthProperty().bind(targetWidthProperty)
|
||||
|
||||
setOnAction {
|
||||
DownloadPatchIntent(LauncherData.INSTANCE.login.activeServer.updateServer ?: return@setOnAction).broadcast()
|
||||
}
|
||||
}
|
||||
button("Scan") {
|
||||
minWidth = 75.0
|
||||
setOnAction {
|
||||
RequestScanIntent(LauncherData.INSTANCE.login.activeServer.updateServer ?: return@setOnAction).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
button("Play") { // TODO: Get proper string for this
|
||||
isFillWidth = true
|
||||
maxWidth = Double.POSITIVE_INFINITY
|
||||
|
||||
setOnAction {
|
||||
val gameInstance = GameInstance(LauncherData.INSTANCE.login.activeServer)
|
||||
gameInstance.forwarder.data.username = LauncherData.INSTANCE.login.activeServer.authentication.username
|
||||
gameInstance.forwarder.data.password = LauncherData.INSTANCE.login.activeServer.authentication.password
|
||||
gameInstance.start()
|
||||
GameLaunchedIntent(gameInstance).broadcast()
|
||||
}
|
||||
|
||||
style {
|
||||
fontSize = 15.px
|
||||
fontWeight = FontWeight.BOLD
|
||||
backgroundColor += Style.playButtonColor
|
||||
textFill = Style.playButtonTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
region { minHeight = 20.0; maxHeight = 20.0 }
|
||||
separator()
|
||||
region { minHeight = 5.0; maxHeight = 5.0 }
|
||||
|
||||
gridpane {
|
||||
this.hgap = 5.0
|
||||
this.vgap = 5.0
|
||||
paddingRight = 5.0
|
||||
|
||||
row {
|
||||
button(messages["servers.login.buttons.website"]) {
|
||||
useMaxWidth = true
|
||||
gridpaneColumnConstraints {
|
||||
hgrow = Priority.ALWAYS
|
||||
}
|
||||
setOnAction {
|
||||
LauncherData.INSTANCE.application.hostServices.showDocument("https://projectswg.com")
|
||||
}
|
||||
}
|
||||
button(messages["servers.login.buttons.create_account"]) {
|
||||
useMaxWidth = true
|
||||
gridpaneColumnConstraints {
|
||||
hgrow = Priority.ALWAYS
|
||||
}
|
||||
setOnAction {
|
||||
// TODO: this, somehow
|
||||
}
|
||||
|
||||
isDisable = true
|
||||
}
|
||||
}
|
||||
|
||||
row {
|
||||
button(messages["servers.login.buttons.configuration"]) {
|
||||
useMaxWidth = true
|
||||
gridpaneColumnConstraints {
|
||||
hgrow = Priority.ALWAYS
|
||||
}
|
||||
setOnAction {
|
||||
// TODO add hyperlink
|
||||
}
|
||||
|
||||
isDisable = true
|
||||
}
|
||||
button(messages["servers.login.buttons.client_options"]) {
|
||||
useMaxWidth = true
|
||||
gridpaneColumnConstraints {
|
||||
hgrow = Priority.ALWAYS
|
||||
}
|
||||
setOnAction {
|
||||
val updateServer = LauncherData.INSTANCE.login.activeServer.updateServer ?: return@setOnAction
|
||||
ProcessExecutor.INSTANCE.buildProcess(updateServer, "SwgClientSetup_r.exe")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
region { vgrow = Priority.ALWAYS }
|
||||
|
||||
label("%s: %s".format(messages["servers.login.launcher_version"], LauncherData.VERSION)) {
|
||||
paddingRight = 5.0
|
||||
|
||||
prefWidthProperty().bind(this@rightBox.widthProperty())
|
||||
alignment = Pos.BASELINE_RIGHT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,24 +18,21 @@
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui
|
||||
package com.projectswg.launcher.resources.gui
|
||||
|
||||
import com.projectswg.launcher.core.resources.gui.settings.SettingsForwarderView
|
||||
import com.projectswg.launcher.core.resources.gui.settings.SettingsGeneralView
|
||||
import com.projectswg.launcher.core.resources.gui.settings.SettingsLoginView
|
||||
import com.projectswg.launcher.core.resources.gui.settings.SettingsUpdateView
|
||||
import tornadofx.View
|
||||
import tornadofx.scrollpane
|
||||
import tornadofx.separator
|
||||
import tornadofx.vbox
|
||||
import com.projectswg.launcher.resources.gui.settings.SettingsForwarderView
|
||||
import com.projectswg.launcher.resources.gui.settings.SettingsGeneralView
|
||||
import com.projectswg.launcher.resources.gui.settings.SettingsLoginView
|
||||
import com.projectswg.launcher.resources.gui.settings.SettingsUpdateView
|
||||
import com.projectswg.launcher.resources.gui.style.Style
|
||||
import tornadofx.*
|
||||
|
||||
class SettingsView : View() {
|
||||
|
||||
override val root = scrollpane {
|
||||
isFitToWidth = true
|
||||
|
||||
vbox {
|
||||
styleClass += "background"
|
||||
addClass(Style.background)
|
||||
|
||||
children.add(find<SettingsGeneralView>().root)
|
||||
separator()
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.projectswg.launcher.core.resources.gui.admin
|
||||
package com.projectswg.launcher.resources.gui.admin
|
||||
|
||||
import com.projectswg.common.network.NetBuffer
|
||||
import com.projectswg.common.network.packets.PacketType
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.projectswg.launcher.core.resources.gui.admin
|
||||
package com.projectswg.launcher.resources.gui.admin
|
||||
|
||||
import com.projectswg.common.network.packets.SWGPacket
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.projectswg.launcher.resources.gui.events
|
||||
|
||||
import tornadofx.FXEvent
|
||||
|
||||
object LauncherClosingEvent : FXEvent()
|
||||
object LauncherNewVersionEvent : FXEvent()
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.projectswg.launcher.resources.gui.servers
|
||||
|
||||
import com.projectswg.launcher.resources.data.LauncherData
|
||||
import com.projectswg.launcher.resources.gui.style.Style
|
||||
import com.projectswg.launcher.resources.intents.DownloadLauncherIntent
|
||||
import javafx.beans.property.ReadOnlyStringWrapper
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.layout.Region
|
||||
import javafx.scene.text.FontWeight
|
||||
import javafx.scene.text.TextAlignment
|
||||
import tornadofx.*
|
||||
|
||||
class LauncherUpdatePopup : Fragment() {
|
||||
|
||||
override val root = vbox {
|
||||
alignment = Pos.CENTER
|
||||
prefWidth = 200.0
|
||||
maxWidth = 200.0
|
||||
prefHeight = 100.0
|
||||
maxHeight = 100.0
|
||||
spacing = 5.0
|
||||
padding = insets(5.0)
|
||||
|
||||
addClass(Style.popup)
|
||||
|
||||
imageview(resources.image("/graphics/large_load_elbow_grease.png")) {
|
||||
fitWidth = 200.0
|
||||
fitHeight = 200.0
|
||||
isPreserveRatio = true
|
||||
alignment = Pos.CENTER
|
||||
}
|
||||
|
||||
label {
|
||||
textProperty().bind(LauncherData.INSTANCE.general.remoteVersionProperty.select {
|
||||
ReadOnlyStringWrapper("A new version of the launcher is available: $it")
|
||||
})
|
||||
|
||||
minHeight = Region.USE_PREF_SIZE
|
||||
textAlignment = TextAlignment.CENTER
|
||||
isWrapText = true
|
||||
style {
|
||||
fontWeight = FontWeight.BOLD
|
||||
}
|
||||
}
|
||||
|
||||
buttonbar {
|
||||
alignment = Pos.CENTER_RIGHT
|
||||
button("Ignore") {
|
||||
action {
|
||||
close()
|
||||
}
|
||||
|
||||
style {
|
||||
backgroundColor += Style.backgroundColorTertiary
|
||||
}
|
||||
}
|
||||
button("Download") {
|
||||
action {
|
||||
DownloadLauncherIntent().broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,14 +18,12 @@
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.core.resources.gui.servers
|
||||
package com.projectswg.launcher.resources.gui.servers
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.LauncherData
|
||||
import com.projectswg.launcher.core.resources.data.announcements.WebsitePostFeed
|
||||
import com.projectswg.launcher.core.resources.data.announcements.WebsitePostMessage
|
||||
import com.projectswg.launcher.core.resources.gui.events.LauncherClosingEvent
|
||||
import com.projectswg.launcher.core.resources.gui.style.Style
|
||||
import javafx.geometry.Pos
|
||||
import com.projectswg.launcher.resources.data.announcements.WebsitePostFeed
|
||||
import com.projectswg.launcher.resources.data.announcements.WebsitePostMessage
|
||||
import com.projectswg.launcher.resources.gui.events.LauncherClosingEvent
|
||||
import com.projectswg.launcher.resources.gui.style.Style
|
||||
import javafx.scene.text.FontWeight
|
||||
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
@@ -51,22 +49,13 @@ class WebsitePostFeedList : View() {
|
||||
}
|
||||
|
||||
onDoubleClick {
|
||||
LauncherData.INSTANCE.application.hostServices.showDocument(item.link)
|
||||
com.projectswg.launcher.resources.data.LauncherData.INSTANCE.application.hostServices.showDocument(item.link)
|
||||
}
|
||||
|
||||
graphic = hbox {
|
||||
maxHeight = 40.0
|
||||
spacing = 10.0
|
||||
|
||||
vbox {
|
||||
alignment = Pos.BASELINE_CENTER
|
||||
|
||||
imageview(resources.image(item.image.imagePath)) {
|
||||
fitHeight = 40.0
|
||||
isPreserveRatio = true
|
||||
isSmooth = true
|
||||
}
|
||||
}
|
||||
vbox {
|
||||
label(item.title) {
|
||||
style {
|
||||
@@ -0,0 +1,71 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.resources.gui.settings
|
||||
|
||||
import com.projectswg.launcher.resources.data.LauncherData
|
||||
import com.projectswg.launcher.resources.data.forwarder.ForwarderData
|
||||
import com.projectswg.launcher.resources.gui.style.Style
|
||||
import javafx.scene.control.TextField
|
||||
import javafx.util.converter.NumberStringConverter
|
||||
import tornadofx.*
|
||||
|
||||
class SettingsForwarderView : View() {
|
||||
|
||||
override val root = vbox {
|
||||
val data = com.projectswg.launcher.resources.data.LauncherData.INSTANCE.forwarder
|
||||
|
||||
label(messages["settings.forwarder.header"]) {
|
||||
addClass(Style.settingsHeaderLabel)
|
||||
}
|
||||
|
||||
lateinit var sendIntervalTextField: TextField
|
||||
lateinit var sendMaxTextField: TextField
|
||||
|
||||
hbox {
|
||||
addClass(Style.settingsRow)
|
||||
label(messages["settings.forwarder.sendInterval"])
|
||||
sendIntervalTextField = textfield(LauncherData.INSTANCE.forwarder.sendInterval.toString()) {
|
||||
textProperty().bindBidirectional(data.sendIntervalProperty, NumberStringConverter())
|
||||
}
|
||||
}
|
||||
|
||||
hbox {
|
||||
addClass(Style.settingsRow)
|
||||
label(messages["settings.forwarder.sendMax"])
|
||||
sendMaxTextField = textfield(LauncherData.INSTANCE.forwarder.sendMax.toString()) {
|
||||
textProperty().bindBidirectional(data.sendMaxProperty, NumberStringConverter())
|
||||
}
|
||||
}
|
||||
|
||||
hbox {
|
||||
addClass(Style.settingsRow)
|
||||
label("")
|
||||
button(messages["settings.forwarder.reset"]) {
|
||||
setOnAction {
|
||||
val textConverter = NumberStringConverter()
|
||||
sendIntervalTextField.text = textConverter.toString(ForwarderData.DEFAULT_SEND_INTERVAL)
|
||||
sendMaxTextField.text = textConverter.toString(ForwarderData.DEFAULT_SEND_MAX)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.resources.gui.settings
|
||||
|
||||
import com.projectswg.launcher.resources.gui.createGlyph
|
||||
import com.projectswg.launcher.resources.gui.style.Style
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import javafx.scene.control.TextField
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.stage.FileChooser
|
||||
import tornadofx.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class SettingsGeneralView : View() {
|
||||
|
||||
override val root = vbox {
|
||||
val data = com.projectswg.launcher.resources.data.LauncherData.INSTANCE.general
|
||||
|
||||
label(messages["settings.general.header"]) {
|
||||
addClass(Style.settingsHeaderLabel)
|
||||
}
|
||||
|
||||
hbox {
|
||||
addClass(Style.settingsRow)
|
||||
label(messages["settings.general.locale"])
|
||||
combobox {
|
||||
items.setAll(Locale.ENGLISH, Locale.GERMAN)
|
||||
valueProperty().bindBidirectional(data.localeProperty)
|
||||
}
|
||||
}
|
||||
|
||||
hbox {
|
||||
addClass(Style.settingsRow)
|
||||
label(messages["settings.general.wine"])
|
||||
val winePathTextField = textfield {
|
||||
isDisable = true
|
||||
textProperty().bindBidirectional(data.wineProperty)
|
||||
}
|
||||
region {
|
||||
prefWidth = 10.0
|
||||
}
|
||||
button {
|
||||
graphic = FontAwesomeIcon.FOLDER_ALT.createGlyph()
|
||||
setOnAction {
|
||||
processWineSelectionButtonAction(winePathTextField)
|
||||
}
|
||||
|
||||
style {
|
||||
prefWidth = 30.px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hbox {
|
||||
addClass(Style.settingsRow)
|
||||
label(messages["settings.general.admin"])
|
||||
checkbox {
|
||||
selectedProperty().bindBidirectional(data.adminProperty)
|
||||
}
|
||||
region {
|
||||
prefWidth = 10.0
|
||||
}
|
||||
label(messages["settings.general.admin_disclaimer"]) {
|
||||
maxWidth = Double.POSITIVE_INFINITY
|
||||
hgrow = Priority.ALWAYS
|
||||
style {
|
||||
fontSize = 10.px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processWineSelectionButtonAction(winePathTextField: TextField) {
|
||||
val selection = chooseWinePath() ?: return
|
||||
try {
|
||||
winePathTextField.text = selection.canonicalPath
|
||||
} catch (ex: IOException) {
|
||||
winePathTextField.text = selection.absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseWinePath(): File? {
|
||||
val fileChooser = FileChooser()
|
||||
fileChooser.title = "Choose Wine Path"
|
||||
val file = fileChooser.showOpenDialog(com.projectswg.launcher.resources.data.LauncherData.INSTANCE.stage)
|
||||
return if (file == null || !file.isFile) null else file
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.resources.gui.settings
|
||||
|
||||
import com.projectswg.launcher.resources.data.LauncherData
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.resources.gui.style.Style
|
||||
import tornadofx.*
|
||||
|
||||
|
||||
class SettingsLoginView : View() {
|
||||
|
||||
override val root = vbox {
|
||||
label(messages["settings.login.header"]) {
|
||||
addClass(Style.settingsHeaderLabel)
|
||||
}
|
||||
|
||||
val localServer = LauncherData.INSTANCE.login.localServer
|
||||
|
||||
hbox {
|
||||
addClass(Style.settingsRow)
|
||||
label(messages["settings.login.local_connection_url"])
|
||||
textfield {
|
||||
textProperty().bindBidirectional(localServer.connectionUriProperty)
|
||||
}
|
||||
}
|
||||
|
||||
hbox {
|
||||
addClass(Style.settingsRow)
|
||||
label(messages["settings.login.update_server"])
|
||||
combobox<UpdateServer> {
|
||||
items = LauncherData.INSTANCE.update.serversProperty
|
||||
valueProperty().bindBidirectional(localServer.updateServerProperty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.resources.gui.settings
|
||||
|
||||
import com.projectswg.launcher.resources.data.LauncherData
|
||||
import com.projectswg.launcher.resources.gui.createGlyph
|
||||
import com.projectswg.launcher.resources.gui.style.Style
|
||||
import com.projectswg.launcher.resources.intents.RequestScanIntent
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import javafx.scene.control.TextField
|
||||
import javafx.stage.DirectoryChooser
|
||||
import tornadofx.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class SettingsUpdateView: View() {
|
||||
|
||||
override val root = vbox {
|
||||
label(messages["settings.update.header"]) {
|
||||
addClass(Style.settingsHeaderLabel)
|
||||
}
|
||||
|
||||
hbox {
|
||||
addClass(Style.settingsRow)
|
||||
label(messages["settings.update.localPath"])
|
||||
val localPathTextField = textfield(LauncherData.INSTANCE.update.localPathProperty) {
|
||||
isDisable = true
|
||||
}
|
||||
region {
|
||||
prefWidth = 10.0
|
||||
}
|
||||
|
||||
button("") {
|
||||
graphic = FontAwesomeIcon.FOLDER_ALT.createGlyph()
|
||||
setOnAction {
|
||||
processLocalPathSelectionButtonAction(localPathTextField)
|
||||
}
|
||||
|
||||
style {
|
||||
prefWidth = 30.px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
region {
|
||||
prefHeight = 10.0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun processLocalPathSelectionButtonAction(localPathTextField: TextField) {
|
||||
val currentFolder = File(localPathTextField.text)
|
||||
val popupFolder = if (currentFolder.exists()) currentFolder else File(System.getProperty("user.home"))
|
||||
val selection = chooseLocalInstallationDirectory(popupFolder) ?: return
|
||||
try {
|
||||
localPathTextField.text = selection.canonicalPath
|
||||
} catch (ex: IOException) {
|
||||
localPathTextField.text = selection.absolutePath
|
||||
}
|
||||
|
||||
for (server in LauncherData.INSTANCE.update.servers) {
|
||||
RequestScanIntent(server).broadcast()
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseLocalInstallationDirectory(currentDirectory: File): File? {
|
||||
val directoryChooser = DirectoryChooser()
|
||||
directoryChooser.title = "Choose Local Installation Path"
|
||||
directoryChooser.initialDirectory = currentDirectory
|
||||
val file = directoryChooser.showDialog(com.projectswg.launcher.resources.data.LauncherData.INSTANCE.stage)
|
||||
return if (file == null || !file.isDirectory) null else file
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2020 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.resources.gui.style
|
||||
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.layout.BorderStrokeStyle
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.text.FontWeight
|
||||
import tornadofx.*
|
||||
|
||||
class Style : Stylesheet() {
|
||||
companion object {
|
||||
// Previous color scheme
|
||||
// val backgroundColorPrimary = c("#484848")
|
||||
// val backgroundColorSecondary = c("#4e4e4e")
|
||||
// val backgroundColorTertiary = c("#313131")
|
||||
// val textColorPrimary = c("#FFFFFF")
|
||||
// val additionalColorPrimary = c("#007fcf")
|
||||
// val additionalColorSecondary = c("#7f7f7f")
|
||||
// val buttonColor = c("#007fcf")
|
||||
|
||||
// New color scheme
|
||||
val backgroundColorPrimary = c("#213639")
|
||||
val backgroundColorSecondary = c("#294749")
|
||||
val backgroundColorTertiary = c("#092729")
|
||||
val textColorPrimary = c("#FFFFFF")
|
||||
val additionalColorPrimary = c("#ff8819")
|
||||
val additionalColorSecondary = c("#6f9c99")
|
||||
val buttonColor = c("#ff8819")
|
||||
|
||||
val playButtonColor: Color = Color.GREEN
|
||||
val playButtonTextColor: Color = textColorPrimary
|
||||
|
||||
val statusNormal by cssclass()
|
||||
val statusFail by cssclass()
|
||||
val statusInProgress by cssclass()
|
||||
val statusGood by cssclass()
|
||||
|
||||
val selectedTabLabel by cssclass()
|
||||
|
||||
// Tables
|
||||
val leftTableCell by cssclass()
|
||||
val centerTableCell by cssclass()
|
||||
|
||||
// Settings
|
||||
val settingsHeaderLabel by cssclass()
|
||||
val settingsRow by cssclass()
|
||||
|
||||
// Server List
|
||||
val serverList by cssclass()
|
||||
|
||||
val background by cssclass()
|
||||
|
||||
val popup by cssclass()
|
||||
}
|
||||
|
||||
init {
|
||||
separator {
|
||||
s(line) {
|
||||
borderStyle += BorderStrokeStyle.SOLID
|
||||
borderWidth += box(3.px, 0.px, 0.px, 0.px)
|
||||
borderColor += box(additionalColorPrimary)
|
||||
}
|
||||
}
|
||||
|
||||
scrollPane {
|
||||
backgroundColor += Color.TRANSPARENT
|
||||
}
|
||||
|
||||
selectedTabLabel {
|
||||
fontSize = 28.px
|
||||
rotate = (-90).deg
|
||||
}
|
||||
|
||||
// Various arrows
|
||||
s(decrementArrow, incrementArrow, arrow) {
|
||||
backgroundColor += textColorPrimary
|
||||
}
|
||||
|
||||
s(filler, track) {
|
||||
backgroundColor += backgroundColorTertiary
|
||||
}
|
||||
|
||||
// ProgressBar, TextField, ListView, and ComboBox inner-component styling
|
||||
s( textField,
|
||||
listView,
|
||||
progressBar child track,
|
||||
comboBox child listCell) {
|
||||
|
||||
backgroundColor += backgroundColorTertiary
|
||||
backgroundRadius += box(0.px)
|
||||
highlightFill = additionalColorPrimary
|
||||
textFill = textColorPrimary
|
||||
}
|
||||
|
||||
// ComboBox, Button, and ScrollBar button styling
|
||||
s( comboBox,
|
||||
button,
|
||||
scrollBar child decrementButton,
|
||||
scrollBar child incrementButton) {
|
||||
|
||||
backgroundColor += additionalColorPrimary
|
||||
textFill = textColorPrimary
|
||||
}
|
||||
|
||||
s(scrollBar child thumb) {
|
||||
backgroundColor += additionalColorSecondary
|
||||
backgroundRadius += box(0.px)
|
||||
backgroundInsets += box(0.px)
|
||||
}
|
||||
|
||||
/* ------------------
|
||||
* ----- Tables -----
|
||||
* ------------------ */
|
||||
cell {
|
||||
backgroundColor += backgroundColorPrimary
|
||||
and(even and filled) {
|
||||
backgroundColor += backgroundColorPrimary
|
||||
// and(hover) {
|
||||
// backgroundColor += backgroundColorHover
|
||||
// }
|
||||
}
|
||||
and(odd and filled) {
|
||||
backgroundColor += backgroundColorSecondary
|
||||
// and(hover) {
|
||||
// backgroundColor += backgroundColorHover
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
tableView {
|
||||
padding = box(0.px)
|
||||
}
|
||||
|
||||
tableColumn {
|
||||
fontWeight = FontWeight.BOLD
|
||||
borderWidth += box(0.px)
|
||||
}
|
||||
|
||||
columnHeader {
|
||||
backgroundColor += backgroundColorTertiary
|
||||
}
|
||||
|
||||
columnHeaderBackground {
|
||||
borderColor += box(additionalColorPrimary)
|
||||
borderWidth += box(0.px, 0.px, 3.px, 0.px)
|
||||
}
|
||||
|
||||
leftTableCell {
|
||||
alignment = Pos.CENTER_LEFT
|
||||
}
|
||||
|
||||
centerTableCell {
|
||||
alignment = Pos.CENTER
|
||||
}
|
||||
|
||||
// OTHER
|
||||
s(text, label, content) {
|
||||
fill = textColorPrimary
|
||||
textFill = textColorPrimary
|
||||
}
|
||||
|
||||
// Buttons
|
||||
s(comboBox, button, decrementButton, incrementButton) {
|
||||
textFill = textColorPrimary
|
||||
backgroundColor += buttonColor
|
||||
backgroundInsets += box(0.px)
|
||||
backgroundRadius += box(0.px)
|
||||
}
|
||||
|
||||
statusNormal {
|
||||
s(text) {
|
||||
fill = Color.WHITE
|
||||
}
|
||||
}
|
||||
statusFail {
|
||||
s(text) {
|
||||
fill = c("#FF0000")
|
||||
}
|
||||
}
|
||||
statusInProgress {
|
||||
s(text) {
|
||||
fill = Color.YELLOW
|
||||
}
|
||||
}
|
||||
statusGood {
|
||||
s(text) {
|
||||
fill = c("#00FF00")
|
||||
}
|
||||
}
|
||||
|
||||
settingsHeaderLabel {
|
||||
fontSize = 14.px
|
||||
fontWeight = FontWeight.BOLD
|
||||
padding = box(5.px, 0.px, 10.px, 5.px)
|
||||
}
|
||||
|
||||
settingsRow {
|
||||
prefHeight = 25.px
|
||||
padding = box(5.px, 5.px, 5.px, 50.px)
|
||||
hgap = 5.px
|
||||
|
||||
s(button) {
|
||||
prefWidth = 150.px
|
||||
}
|
||||
|
||||
s(label) {
|
||||
prefWidth = 150.px
|
||||
prefHeight= 25.px
|
||||
}
|
||||
|
||||
s(checkBox) {
|
||||
prefHeight = 25.px
|
||||
}
|
||||
|
||||
s(comboBox, textField) {
|
||||
prefWidth = 400.px
|
||||
prefHeight = 20.px
|
||||
}
|
||||
}
|
||||
|
||||
// Server List
|
||||
serverList {
|
||||
// padding = box(5.px)
|
||||
}
|
||||
|
||||
background {
|
||||
backgroundColor += backgroundColorPrimary
|
||||
}
|
||||
|
||||
s(".tab-header-background") {
|
||||
backgroundColor += backgroundColorPrimary
|
||||
}
|
||||
|
||||
s(tabContentArea) {
|
||||
backgroundColor += backgroundColorPrimary
|
||||
}
|
||||
|
||||
tab {
|
||||
backgroundColor += backgroundColorSecondary
|
||||
|
||||
and(selected) {
|
||||
backgroundColor += backgroundColorTertiary
|
||||
}
|
||||
}
|
||||
|
||||
s(focusIndicator) {
|
||||
borderColor += box(Color.TRANSPARENT)
|
||||
}
|
||||
|
||||
popup {
|
||||
backgroundColor += backgroundColorPrimary
|
||||
borderWidth += box(5.px)
|
||||
borderColor += box(Color.WHITE)
|
||||
|
||||
s(label) {
|
||||
fill = Color.WHITE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.projectswg.launcher.core.resources.intents
|
||||
package com.projectswg.launcher.resources.intents
|
||||
|
||||
import com.projectswg.launcher.core.resources.game.GameInstance
|
||||
import com.projectswg.launcher.resources.game.GameInstance
|
||||
import me.joshlarson.jlcommon.control.Intent
|
||||
|
||||
data class GameLaunchedIntent(val gameInstance: GameInstance): Intent()
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.projectswg.launcher.core.resources.intents
|
||||
package com.projectswg.launcher.resources.intents
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer
|
||||
import me.joshlarson.jlcommon.control.Intent
|
||||
|
||||
data class CancelDownloadIntent(val server: UpdateServer): Intent()
|
||||
data class DownloadPatchIntent(val server: UpdateServer): Intent()
|
||||
data class RequestScanIntent(val server: UpdateServer): Intent()
|
||||
class DownloadLauncherIntent: Intent()
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.projectswg.launcher.resources.pipeline
|
||||
|
||||
import com.projectswg.launcher.resources.data.LauncherData
|
||||
import com.projectswg.launcher.resources.gui.events.LauncherNewVersionEvent
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import me.joshlarson.json.JSONInputStream
|
||||
import tornadofx.FX
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
object LauncherConfigurationUpdater {
|
||||
|
||||
private const val REMOTE_CONFIGURATION_URL = "https://projectswg.com/launcher/launcher.json"
|
||||
|
||||
private val downloadUrl = AtomicReference<String>(null)
|
||||
|
||||
fun update() {
|
||||
try {
|
||||
Log.d("Retrieving server configuration from %s", REMOTE_CONFIGURATION_URL)
|
||||
JSONInputStream(URL(REMOTE_CONFIGURATION_URL).openStream()).use { input ->
|
||||
val configuration = input.readObject()
|
||||
Log.t("Remote configuration: %s", configuration)
|
||||
val version = configuration["download_version"] as? String ?: return
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val download = configuration["download"] as? Map<String, Any?> ?: return
|
||||
val downloadUrl = download[getOS()] as? String ?: return
|
||||
val needsUpdate = isNewVersionAvailable(version)
|
||||
|
||||
Log.d("Server configuration: version=%s download_url=%s needs_update=%s", version, downloadUrl, needsUpdate)
|
||||
LauncherData.INSTANCE.general.remoteVersion = version
|
||||
this.downloadUrl.set(downloadUrl)
|
||||
if (needsUpdate)
|
||||
FX.eventbus.fire(LauncherNewVersionEvent)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e("Failed to retrieve updated server configuration")
|
||||
}
|
||||
}
|
||||
|
||||
fun download() {
|
||||
val downloadUrl = this.downloadUrl.get()
|
||||
if (downloadUrl == null) {
|
||||
Log.w("Cannot update - download url is empty")
|
||||
return
|
||||
}
|
||||
|
||||
Log.i("Launching download URL: %s", downloadUrl)
|
||||
LauncherData.INSTANCE.application.hostServices.showDocument(downloadUrl)
|
||||
}
|
||||
|
||||
private fun isNewVersionAvailable(specifiedVersionStr: String?): Boolean {
|
||||
if (specifiedVersionStr == null) return true
|
||||
val currentVersion = LauncherData.VERSION.split(".")
|
||||
val specifiedVersion = specifiedVersionStr.split(".")
|
||||
|
||||
for ((cur, spec) in currentVersion.zip(specifiedVersion)) {
|
||||
val curInt = Integer.parseUnsignedInt(cur)
|
||||
val specInt = Integer.parseUnsignedInt(spec)
|
||||
if (curInt == specInt)
|
||||
continue
|
||||
|
||||
if (curInt < specInt)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getOS(): String {
|
||||
val os = System.getProperty("os.name").lowercase(Locale.US)
|
||||
if (os.contains("win"))
|
||||
return "windows"
|
||||
if (os.contains("mac"))
|
||||
return "mac"
|
||||
return "linux"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
package com.projectswg.launcher.resources.pipeline
|
||||
|
||||
import com.projectswg.launcher.resources.data.LauncherData
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer.RequiredFile
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer.UpdateServerStatus
|
||||
import javafx.application.Platform
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import me.joshlarson.json.JSONException
|
||||
import me.joshlarson.json.JSONInputStream
|
||||
import me.joshlarson.json.JSONOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
import java.util.stream.Collectors
|
||||
|
||||
object UpdateServerUpdater {
|
||||
|
||||
fun update(server: UpdateServer) {
|
||||
val localPath = LauncherData.INSTANCE.update.localPath
|
||||
if (!File(localPath).isDirectory) {
|
||||
Log.e("Not a valid local path: %s", localPath)
|
||||
return
|
||||
}
|
||||
val info = UpdateServerDownloaderInfo(server, localPath)
|
||||
if (!updateFileList(info))
|
||||
return
|
||||
filterValidFiles(info)
|
||||
updateServerStatus(info)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage 1: Download the file list from the update server, or fall back on the local copy. If neither are accessible, fail.
|
||||
*/
|
||||
private fun updateFileList(info: UpdateServerDownloaderInfo): Boolean {
|
||||
Log.t("Retrieving latest file list from %s...", info.url)
|
||||
val localFileList = File(info.localPath, "files.json")
|
||||
var files: List<Any?>
|
||||
try {
|
||||
JSONInputStream(createURL(info, "files.json").openConnection().getInputStream()).use { `in` ->
|
||||
files = `in`.readArray()
|
||||
try {
|
||||
JSONOutputStream(FileOutputStream(localFileList)).use { out -> out.writeArray(files) }
|
||||
} catch (e: IOException) {
|
||||
Log.e("Failed to write updated file list to disk for update server %s (%s: %s)", info.name, e.javaClass.name, e.message)
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.w("Failed to retrieve latest file list for update server %s (%s: %s). Falling back on local copy...", e.javaClass.name, e.message, info.name)
|
||||
try {
|
||||
JSONInputStream(FileInputStream(localFileList)).use { `in` -> files = `in`.readArray() }
|
||||
} catch (t: JSONException) {
|
||||
Log.e("Failed to read file list from disk on update server %s with path %s. Aborting update.", info.name, localFileList)
|
||||
return false
|
||||
} catch (t: IOException) {
|
||||
Log.e("Failed to read file list from disk on update server %s with path %s. Aborting update.", info.name, localFileList)
|
||||
return false
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
Log.w("Failed to retrieve latest file list for update server %s (%s: %s). Falling back on local copy...", e.javaClass.name, e.message, info.name)
|
||||
try {
|
||||
JSONInputStream(FileInputStream(localFileList)).use { `in` -> files = `in`.readArray() }
|
||||
} catch (t: JSONException) {
|
||||
Log.e("Failed to read file list from disk on update server %s with path %s. Aborting update.", info.name, localFileList)
|
||||
return false
|
||||
} catch (t: IOException) {
|
||||
Log.e("Failed to read file list from disk on update server %s with path %s. Aborting update.", info.name, localFileList)
|
||||
return false
|
||||
}
|
||||
}
|
||||
info.files = files.stream()
|
||||
.filter { obj: Any? -> MutableMap::class.java.isInstance(obj) }
|
||||
.map { obj: Any? -> MutableMap::class.java.cast(obj) }
|
||||
.map { obj -> @Suppress("UNCHECKED_CAST") jsonObjectToRequiredFile(info, obj as? Map<String, Any> ?: return@map null ) }
|
||||
.filter { it != null }
|
||||
.collect(Collectors.toList())
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage 2: Scan each file and only keep the ones that need to be downloaded.
|
||||
*/
|
||||
private fun filterValidFiles(info: UpdateServerDownloaderInfo) {
|
||||
val files: MutableList<RequiredFile> = Objects.requireNonNull<MutableList<RequiredFile>?>(info.files, "File list was not read correctly")
|
||||
Log.d("%d known files. Scanning...", files.size)
|
||||
val total = files.size
|
||||
Platform.runLater { info.server.status = UpdateServerStatus.SCANNING }
|
||||
files.removeIf { obj: RequiredFile -> isValidFile(obj) }
|
||||
val valid = total - files.size
|
||||
Log.d("Completed scan of update server %s. %d of %d valid.", info.name, valid, total)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage 3: Update the UpdateServer status and the required files.
|
||||
*/
|
||||
private fun updateServerStatus(info: UpdateServerDownloaderInfo) {
|
||||
val serverList: MutableList<RequiredFile> = info.server.requiredFiles
|
||||
val updateList: List<RequiredFile> = info.files ?: return
|
||||
val updateStatus = if (updateList.isEmpty()) UpdateServerStatus.READY else UpdateServerStatus.REQUIRES_DOWNLOAD
|
||||
serverList.clear()
|
||||
serverList.addAll(updateList)
|
||||
Platform.runLater { info.server.status = updateStatus }
|
||||
Log.d("Setting update server '%s' status to %s", info.name, updateStatus)
|
||||
}
|
||||
|
||||
private fun isValidFile(file: RequiredFile): Boolean {
|
||||
val localFile = file.localPath
|
||||
val length = localFile.length()
|
||||
return localFile.isFile && length == file.length
|
||||
}
|
||||
|
||||
private fun jsonObjectToRequiredFile(info: UpdateServerDownloaderInfo, obj: Map<String, Any>): RequiredFile {
|
||||
val path = obj["path"] as String? ?: throw RuntimeException("no path defined for file")
|
||||
return try {
|
||||
RequiredFile(File(info.localPath, path), createURL(info, path), obj["length"] as Long, (obj["xxhash"] as String?)!!)
|
||||
} catch (e: MalformedURLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(MalformedURLException::class)
|
||||
private fun createURL(info: UpdateServerDownloaderInfo, path: String): URL {
|
||||
var url = info.url
|
||||
while (url.endsWith("/"))
|
||||
url = url.removeSuffix("/")
|
||||
|
||||
return URL(url + "/" + path.removePrefix("/"))
|
||||
}
|
||||
|
||||
private class UpdateServerDownloaderInfo(val server: UpdateServer, localPath: String) {
|
||||
val name: String = server.name
|
||||
val url: String = server.url
|
||||
val localPath: File = File(localPath, server.gameVersion)
|
||||
|
||||
var files: MutableList<RequiredFile>? = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
package com.projectswg.launcher.services.data
|
||||
|
||||
import me.joshlarson.jlcommon.control.Manager
|
||||
import me.joshlarson.jlcommon.control.ManagerStructure
|
||||
|
||||
@ManagerStructure(children = [
|
||||
PreferencesDataService::class,
|
||||
DownloadService::class,
|
||||
RemoteDataService::class
|
||||
])
|
||||
class DataManager : Manager()
|
||||
@@ -18,14 +18,14 @@
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.services.data
|
||||
package com.projectswg.launcher.services.data
|
||||
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer.RequiredFile
|
||||
import com.projectswg.launcher.core.resources.data.update.UpdateServer.UpdateServerStatus
|
||||
import com.projectswg.launcher.core.resources.intents.CancelDownloadIntent
|
||||
import com.projectswg.launcher.core.resources.intents.DownloadPatchIntent
|
||||
import com.projectswg.launcher.core.resources.intents.RequestScanIntent
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer.RequiredFile
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer.UpdateServerStatus
|
||||
import com.projectswg.launcher.resources.intents.CancelDownloadIntent
|
||||
import com.projectswg.launcher.resources.intents.DownloadPatchIntent
|
||||
import com.projectswg.launcher.resources.intents.RequestScanIntent
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.binding.NumberBinding
|
||||
import javafx.beans.property.LongProperty
|
||||
@@ -0,0 +1,333 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
package com.projectswg.launcher.services.data
|
||||
|
||||
import com.projectswg.launcher.resources.data.LauncherData
|
||||
import com.projectswg.launcher.resources.data.login.AuthenticationData
|
||||
import com.projectswg.launcher.resources.data.login.LoginServer
|
||||
import com.projectswg.launcher.resources.data.update.UpdateServer
|
||||
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool
|
||||
import me.joshlarson.jlcommon.control.Service
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import me.joshlarson.json.JSONInputStream
|
||||
import me.joshlarson.json.JSONOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
|
||||
class PreferencesDataService : Service() {
|
||||
|
||||
private val executor: ScheduledThreadPool = ScheduledThreadPool(1, 3, "data-executor-%d")
|
||||
|
||||
init {
|
||||
loadPreferences()
|
||||
}
|
||||
|
||||
override fun start(): Boolean {
|
||||
createDefaults()
|
||||
executor.start()
|
||||
executor.executeWithFixedDelay((5 * 60000).toLong(), (5 * 60000).toLong()) { savePreferences() }
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stop(): Boolean {
|
||||
savePreferences()
|
||||
executor.stop()
|
||||
return executor.awaitTermination(1000)
|
||||
}
|
||||
|
||||
private fun createDefaults() {
|
||||
if (LauncherData.INSTANCE.general.wine == null || LauncherData.INSTANCE.general.wine!!.isEmpty())
|
||||
LauncherData.INSTANCE.general.wine = winePath
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun loadPreferences() {
|
||||
val applicationDataDirectory = LauncherData.getApplicationDataDirectory()
|
||||
if (!applicationDataDirectory.exists())
|
||||
applicationDataDirectory.mkdirs()
|
||||
else if (!applicationDataDirectory.isDirectory) {
|
||||
applicationDataDirectory.deleteRecursively()
|
||||
applicationDataDirectory.mkdirs()
|
||||
}
|
||||
|
||||
lateinit var lastSelectedServerName: String
|
||||
lateinit var localServerConfiguration: Map<String, Any?>
|
||||
|
||||
try {
|
||||
JSONInputStream(FileInputStream(File(LauncherData.getApplicationDataDirectory(), "settings.json"))).use {
|
||||
val settings = it.readObject()
|
||||
loadSettingsGeneral(settings.getMap("general"))
|
||||
loadSettingsUpdate(settings.getMap("update"))
|
||||
loadSettingsLogin(settings.getMap("login"))
|
||||
loadSettingsForwarder(settings.getMap("forwarder"))
|
||||
|
||||
lastSelectedServerName = settings.getMap("login").getString("last_selected_server", "")
|
||||
localServerConfiguration = settings.getMap("login").getMap("local_server")
|
||||
}
|
||||
} catch (fnf: FileNotFoundException) {
|
||||
loadSettingsGeneral(mapOf())
|
||||
loadSettingsLogin(mapOf())
|
||||
loadSettingsForwarder(mapOf())
|
||||
}
|
||||
|
||||
loadServers()
|
||||
|
||||
// Add the local server last so that it's always last in the menu
|
||||
loadLocalServer(localServerConfiguration)
|
||||
|
||||
val lastSelectedServer = LauncherData.INSTANCE.login.getServerByName(lastSelectedServerName)
|
||||
if (lastSelectedServer != null)
|
||||
LauncherData.INSTANCE.login.activeServer = lastSelectedServer
|
||||
}
|
||||
|
||||
private fun loadSettingsGeneral(settings: Map<String, Any?>) {
|
||||
LauncherData.INSTANCE.general.locale = Locale.forLanguageTag(settings.getString("locale", Locale.US.toLanguageTag()))
|
||||
LauncherData.INSTANCE.general.wine = settings.getString("wine", "")
|
||||
LauncherData.INSTANCE.general.isAdmin = settings.getBool("admin", false)
|
||||
}
|
||||
|
||||
private fun loadSettingsUpdate(settings: Map<String, Any?>) {
|
||||
LauncherData.INSTANCE.update.localPath = settings.getString("local_path", "")
|
||||
}
|
||||
|
||||
private fun loadSettingsLogin(settings: Map<String, Any?>) {
|
||||
for (e in settings.getMap("authentications")) {
|
||||
val data = AuthenticationData(e.key)
|
||||
data.username = castMap(e.value).getString("username", "")
|
||||
data.password = castMap(e.value).getString("password", "")
|
||||
LauncherData.INSTANCE.login.authenticationData.add(data)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadLocalServer(localServerSettings: Map<String, Any?>) {
|
||||
val localAuthenticationSource = AuthenticationData(LOCAL_AUTHENTICATION_NAME)
|
||||
if (LauncherData.INSTANCE.login.getAuthenticationData(LOCAL_AUTHENTICATION_NAME) == null)
|
||||
LauncherData.INSTANCE.login.authenticationData.add(localAuthenticationSource)
|
||||
|
||||
val defaultLocalServer = LoginServer("local")
|
||||
defaultLocalServer.connectionUri = "ws://127.0.0.1:44463/game"
|
||||
defaultLocalServer.updateServer = LauncherData.INSTANCE.update.serversProperty.getOrNull(0)
|
||||
defaultLocalServer.authentication = localAuthenticationSource
|
||||
|
||||
val localServer = loadLoginServer(localServerSettings) ?: defaultLocalServer
|
||||
LauncherData.INSTANCE.login.localServer = localServer
|
||||
LauncherData.INSTANCE.login.servers.add(LauncherData.INSTANCE.login.localServer)
|
||||
}
|
||||
|
||||
private fun loadSettingsForwarder(settings: Map<String, Any?>) {
|
||||
LauncherData.INSTANCE.forwarder.sendInterval = settings.getInt("send_interval", 1000)
|
||||
LauncherData.INSTANCE.forwarder.sendMax = settings.getInt("send_max", 400)
|
||||
}
|
||||
|
||||
private fun loadServers() {
|
||||
try {
|
||||
loadRemoteServers()
|
||||
JSONInputStream(FileInputStream(File(LauncherData.getApplicationDataDirectory(), "servers.json"))).use {
|
||||
val serverConfigurations = it.readObject()
|
||||
loadUpdateServers(serverConfigurations.getListOfMap("update_servers"))
|
||||
loadLoginServers(serverConfigurations.getListOfMap("login_servers"))
|
||||
}
|
||||
} catch (fnf: FileNotFoundException) {
|
||||
loadSettingsGeneral(mapOf())
|
||||
loadSettingsLogin(mapOf())
|
||||
loadSettingsForwarder(mapOf())
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadRemoteServers() {
|
||||
try {
|
||||
Log.d("Retrieving server configuration from %s", REMOTE_SERVER_CONFIGURATION_URL)
|
||||
JSONInputStream(URL(REMOTE_SERVER_CONFIGURATION_URL).openStream()).use { input ->
|
||||
JSONOutputStream(FileOutputStream(File(LauncherData.getApplicationDataDirectory(), "servers.json"))).use { output ->
|
||||
output.writeObject(input.readObject())
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e("Failed to retrieve updated server configuration")
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadUpdateServers(serverConfigurations: List<Map<String, Any?>>) {
|
||||
for (updateServerConfiguration in serverConfigurations) {
|
||||
val updateServer = UpdateServer(updateServerConfiguration["name"] as? String ?: continue)
|
||||
updateServer.url = updateServerConfiguration["url"] as? String ?: continue
|
||||
updateServer.gameVersion = updateServerConfiguration["game_version"] as? String ?: continue
|
||||
updateServer.friendlyName = updateServerConfiguration["friendly_name"] as? String ?: continue
|
||||
LauncherData.INSTANCE.update.servers.add(updateServer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadLoginServers(serverConfigurations: List<Map<String, Any?>>) {
|
||||
val unusedAuthenticationServers = HashSet(LauncherData.INSTANCE.login.authenticationData)
|
||||
for (loginServerConfiguration in serverConfigurations) {
|
||||
val loginServer = loadLoginServer(loginServerConfiguration) ?: continue
|
||||
|
||||
unusedAuthenticationServers.remove(loginServer.authentication)
|
||||
|
||||
LauncherData.INSTANCE.login.servers.add(loginServer)
|
||||
}
|
||||
|
||||
// Don't want to perpetually store credentials
|
||||
for (unusedAuthentication in unusedAuthenticationServers) {
|
||||
if (unusedAuthentication.name == LOCAL_AUTHENTICATION_NAME)
|
||||
continue // This will be used later
|
||||
LauncherData.INSTANCE.login.authenticationData.remove(unusedAuthentication)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadLoginServer(loginServerConfiguration: Map<String, Any?>): LoginServer? {
|
||||
val loginServer = LoginServer(loginServerConfiguration["name"] as? String ?: return null)
|
||||
loginServer.connectionUri = loginServerConfiguration["url"] as? String ?: return null
|
||||
loginServer.updateServer = LauncherData.INSTANCE.update.getServer(loginServerConfiguration["update_server"] as? String ?: return null) ?: return null
|
||||
val authenticationSource = loginServerConfiguration["authentication_source"] as? String ?: return null
|
||||
var authentication = LauncherData.INSTANCE.login.getAuthenticationData(authenticationSource)
|
||||
if (authentication == null) {
|
||||
authentication = AuthenticationData(loginServerConfiguration["authentication_source"] as? String ?: return null)
|
||||
LauncherData.INSTANCE.login.authenticationData.add(authentication)
|
||||
}
|
||||
loginServer.authentication = authentication
|
||||
return loginServer
|
||||
}
|
||||
|
||||
private fun castMap(obj: Any?): Map<String, Any?> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return obj as? Map<String, Any?> ?: mapOf()
|
||||
}
|
||||
|
||||
private fun castListOfMap(obj: Any?): List<Map<String, Any?>> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return obj as? List<Map<String, Any?>> ?: listOf()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun savePreferences() {
|
||||
JSONOutputStream(FileOutputStream(File(LauncherData.getApplicationDataDirectory(), "settings.json"))).use {
|
||||
it.writeObject(jsonSettings())
|
||||
}
|
||||
}
|
||||
|
||||
private fun jsonSettings(): Map<String, Any?> {
|
||||
return mapOf(
|
||||
"general" to jsonSettingsGeneral(),
|
||||
"update" to jsonSettingsUpdate(),
|
||||
"login" to jsonSettingsLogin(),
|
||||
"forwarder" to jsonSettingsForwarder()
|
||||
)
|
||||
}
|
||||
|
||||
private fun jsonSettingsGeneral(): Map<String, Any?> {
|
||||
return mapOf(
|
||||
"locale" to LauncherData.INSTANCE.general.locale.toLanguageTag(),
|
||||
"wine" to LauncherData.INSTANCE.general.wine,
|
||||
"admin" to LauncherData.INSTANCE.general.isAdmin
|
||||
)
|
||||
}
|
||||
|
||||
private fun jsonSettingsUpdate(): Map<String, Any?> {
|
||||
return mapOf(
|
||||
"local_path" to LauncherData.INSTANCE.update.localPath
|
||||
)
|
||||
}
|
||||
|
||||
private fun jsonSettingsLogin(): Map<String, Any?> {
|
||||
val localServer = LauncherData.INSTANCE.login.localServer
|
||||
|
||||
return mapOf(
|
||||
"last_selected_server" to LauncherData.INSTANCE.login.lastSelectedServer,
|
||||
"authentications" to jsonSettingsLoginAuthentication(),
|
||||
"local_server" to mapOf(
|
||||
"name" to localServer.name,
|
||||
"url" to localServer.connectionUri,
|
||||
"update_server" to (localServer.updateServer?.name ?: ""),
|
||||
"authentication_source" to localServer.authentication.name
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun jsonSettingsLoginAuthentication(): Map<String, Any?> {
|
||||
val authentications = HashMap<String, Any?>()
|
||||
|
||||
for (auth in LauncherData.INSTANCE.login.authenticationData) {
|
||||
authentications[auth.name] = mapOf(
|
||||
"username" to auth.username,
|
||||
"password" to auth.password
|
||||
)
|
||||
}
|
||||
|
||||
return authentications
|
||||
}
|
||||
|
||||
private fun jsonSettingsForwarder(): Map<String, Any?> {
|
||||
return mapOf(
|
||||
"send_interval" to LauncherData.INSTANCE.forwarder.sendInterval,
|
||||
"send_max" to LauncherData.INSTANCE.forwarder.sendMax
|
||||
)
|
||||
}
|
||||
|
||||
private fun Map<String, Any?>?.getMap(key: String): Map<String, Any?> {
|
||||
return castMap(this?.getOrDefault(key, mapOf<String, Any?>()))
|
||||
}
|
||||
|
||||
private fun Map<String, Any?>?.getListOfMap(key: String): List<Map<String, Any?>> {
|
||||
return castListOfMap(this?.getOrDefault(key, listOf<Map<String, Any?>>()))
|
||||
}
|
||||
|
||||
private fun Map<String, Any?>?.getString(key: String, defaultValue: String): String {
|
||||
return (this?.getOrDefault(key, defaultValue) as? String?) ?: defaultValue
|
||||
}
|
||||
|
||||
private fun Map<String, Any?>?.getInt(key: String, defaultValue: Int): Int {
|
||||
return ((this?.getOrDefault(key, defaultValue) as? Number?) ?: defaultValue).toInt()
|
||||
}
|
||||
|
||||
private fun Map<String, Any?>?.getBool(key: String, defaultValue: Boolean): Boolean {
|
||||
return (this?.getOrDefault(key, defaultValue) as? Boolean?) ?: defaultValue
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val LOCAL_AUTHENTICATION_NAME = "local"
|
||||
private const val REMOTE_SERVER_CONFIGURATION_URL = "https://projectswg.com/launcher/servers.json"
|
||||
|
||||
private val winePath: String?
|
||||
get() {
|
||||
val pathStr = System.getenv("PATH") ?: return null
|
||||
for (path in pathStr.split(File.pathSeparator.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
|
||||
Log.t("Testing wine binary at %s", path)
|
||||
var test = File(path, "wine")
|
||||
if (test.isFile) {
|
||||
try {
|
||||
test = test.canonicalFile
|
||||
Log.d("Found wine installation. Location: %s", test)
|
||||
return test.absolutePath
|
||||
} catch (e: IOException) {
|
||||
Log.w("Failed to get canonical file location of possible wine location: %s", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https:></https:>//www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.services.data
|
||||
|
||||
import com.projectswg.launcher.resources.intents.DownloadLauncherIntent
|
||||
import com.projectswg.launcher.resources.intents.RequestScanIntent
|
||||
import com.projectswg.launcher.resources.pipeline.LauncherConfigurationUpdater
|
||||
import com.projectswg.launcher.resources.pipeline.UpdateServerUpdater
|
||||
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool
|
||||
import me.joshlarson.jlcommon.control.IntentHandler
|
||||
import me.joshlarson.jlcommon.control.Service
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class RemoteDataService : Service() {
|
||||
|
||||
private val executor: ScheduledThreadPool = ScheduledThreadPool(2, "remote-data-service")
|
||||
|
||||
override fun start(): Boolean {
|
||||
executor.start()
|
||||
// Retrieves the latest file list for each update server
|
||||
executor.executeWithFixedDelay(0, TimeUnit.MINUTES.toMillis(30)) { this.updateUpdateServers() }
|
||||
executor.executeWithFixedDelay(0, TimeUnit.MINUTES.toMillis(30)) { this.updateRemoteVersion() }
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stop(): Boolean {
|
||||
executor.stop()
|
||||
return executor.awaitTermination(1000)
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private fun handleRequestScanIntent(rsi: RequestScanIntent) {
|
||||
UpdateServerUpdater.update(rsi.server)
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private fun handleDownloadLauncherIntent(dli: DownloadLauncherIntent) {
|
||||
LauncherConfigurationUpdater.download()
|
||||
}
|
||||
|
||||
private fun updateUpdateServers() {
|
||||
updateData.servers.parallelStream().forEach { UpdateServerUpdater.update(it) }
|
||||
}
|
||||
|
||||
private fun updateRemoteVersion() {
|
||||
LauncherConfigurationUpdater.update()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val updateData: com.projectswg.launcher.resources.data.update.UpdateData
|
||||
get() = com.projectswg.launcher.resources.data.LauncherData.INSTANCE.update
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,10 +18,10 @@
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.services.launcher
|
||||
package com.projectswg.launcher.services.launcher
|
||||
|
||||
import com.projectswg.launcher.core.resources.game.GameInstance
|
||||
import com.projectswg.launcher.core.resources.intents.GameLaunchedIntent
|
||||
import com.projectswg.launcher.resources.game.GameInstance
|
||||
import com.projectswg.launcher.resources.intents.GameLaunchedIntent
|
||||
import me.joshlarson.jlcommon.control.IntentHandler
|
||||
import me.joshlarson.jlcommon.control.Service
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
@@ -18,7 +18,7 @@
|
||||
* *
|
||||
*/
|
||||
|
||||
package com.projectswg.launcher.core.services.launcher
|
||||
package com.projectswg.launcher.services.launcher
|
||||
|
||||
import me.joshlarson.jlcommon.control.Manager
|
||||
import me.joshlarson.jlcommon.control.ManagerStructure
|
||||
@@ -4,13 +4,11 @@ open module com.projectswg.launcher {
|
||||
requires me.joshlarson.jlcommon;
|
||||
requires me.joshlarson.jlcommon.javafx;
|
||||
requires fast.json;
|
||||
requires net.openhft.hashing;
|
||||
requires org.bouncycastle.provider;
|
||||
requires org.jetbrains.annotations;
|
||||
|
||||
requires com.projectswg.common;
|
||||
requires com.projectswg.forwarder;
|
||||
requires com.projectswg.holocore.client;
|
||||
|
||||
requires jdk.crypto.ec;
|
||||
requires java.prefs;
|
||||
@@ -25,6 +23,4 @@ open module com.projectswg.launcher {
|
||||
|
||||
requires kotlin.stdlib;
|
||||
requires kotlin.reflect;
|
||||
|
||||
exports com.projectswg.launcher.core.resources.data.forwarder;
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?import com.projectswg.launcher.core.resources.gui.CardContainer?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox fx:id="root" fx:controller="com.projectswg.launcher.core.resources.gui.AnnouncementsView" xmlns:fx="http://javafx.com/fxml">
|
||||
<CardContainer fx:id="cardContainer" styleClass="card-container" VBox.vgrow="ALWAYS" />
|
||||
</VBox>
|
||||
@@ -1,32 +0,0 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<?import javafx.scene.Group?>
|
||||
<AnchorPane fx:id="root" fx:controller="com.projectswg.launcher.core.resources.gui.NavigationView" xmlns:fx="http://javafx.com/fxml" prefWidth="825" prefHeight="640">
|
||||
<stylesheets>
|
||||
<URL value="@/css/theme.css"/>
|
||||
</stylesheets>
|
||||
<TabPane fx:id="tabPane" side="LEFT" VBox.vgrow="ALWAYS" AnchorPane.topAnchor="0" AnchorPane.rightAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0">
|
||||
<Tab fx:id="announcementsTab" styleClass="background" text="%announcements" closable="false">
|
||||
<tooltip>
|
||||
<Tooltip text="%announcements" />
|
||||
</tooltip>
|
||||
<fx:include source="AnnouncementView.fxml"/>
|
||||
</Tab>
|
||||
<Tab fx:id="serverListTab" styleClass="background" text="%servers" closable="false">
|
||||
<tooltip>
|
||||
<Tooltip text="%servers" />
|
||||
</tooltip>
|
||||
<fx:include source="ServerListView.fxml"/>
|
||||
</Tab>
|
||||
<Tab fx:id="settingsTab" styleClass="background" text="%settings" closable="false">
|
||||
<tooltip>
|
||||
<Tooltip text="%settings" />
|
||||
</tooltip>
|
||||
<fx:include source="SettingsView.fxml"/>
|
||||
</Tab>
|
||||
</TabPane>
|
||||
<Group AnchorPane.bottomAnchor="15" AnchorPane.leftAnchor="5">
|
||||
<Label fx:id="selectedTabLabel" />
|
||||
</Group>
|
||||
</AnchorPane>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?import com.projectswg.launcher.core.resources.gui.CardContainer?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox fx:id="root" fx:controller="com.projectswg.launcher.core.resources.gui.ServerListView" xmlns:fx="http://javafx.com/fxml">
|
||||
<ImageView fx:id="headerImage">
|
||||
<Image url="/graphics/headers/server-table.png"/>
|
||||
</ImageView>
|
||||
<TableView fx:id="serverTable" focusTraversable="false">
|
||||
<placeholder>
|
||||
<Label text="%noServers"/>
|
||||
</placeholder>
|
||||
</TableView>
|
||||
<Region prefHeight="5" />
|
||||
<CardContainer fx:id="cardContainer" styleClass="card-container" VBox.vgrow="ALWAYS" />
|
||||
</VBox>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.control.Separator?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<ScrollPane fx:id="root" xmlns:fx="http://javafx.com/fxml" fitToWidth="true">
|
||||
<VBox styleClass="background">
|
||||
<fx:include source="settings/SettingsGeneralView.fxml" />
|
||||
<Separator />
|
||||
<fx:include source="settings/SettingsLoginView.fxml" />
|
||||
<Separator />
|
||||
<fx:include source="/com/projectswg/launcher/core/resources/gui/settings/SettingsUpdateView.fxml" />
|
||||
<Separator />
|
||||
<fx:include source="settings/SettingsForwarderView.fxml" />
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
@@ -1,18 +0,0 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<VBox fx:id="root" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<Label text="%settings.forwarder.header" styleClass="settings-header-label" />
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.forwarder.sendInterval" />
|
||||
<TextField fx:id="sendIntervalTextField" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.forwarder.sendMax" />
|
||||
<TextField fx:id="sendMaxTextField" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label />
|
||||
<Button fx:id="resetButton" text="%settings.forwarder.reset" />
|
||||
</HBox>
|
||||
</VBox>
|
||||
@@ -1,29 +0,0 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<VBox fx:id="root" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<Label text="%settings.general.header" styleClass="settings-header-label" />
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.general.sound" />
|
||||
<CheckBox fx:id="soundCheckbox" disable="true" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.general.theme" />
|
||||
<ComboBox fx:id="themeComboBox" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.general.locale" />
|
||||
<ComboBox fx:id="localeComboBox" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.general.wine" />
|
||||
<TextField fx:id="wineTextField" disable="true" />
|
||||
<Region prefWidth="10" />
|
||||
<Button fx:id="wineSelectionButton" styleClass="pathSelection" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.general.admin" />
|
||||
<CheckBox fx:id="adminCheckBox" />
|
||||
<Region prefWidth="10" />
|
||||
<Label text="%settings.general.admin_disclaimer" style="-fx-font-size: 10px;" maxWidth="Infinity" HBox.hgrow="ALWAYS" />
|
||||
</HBox>
|
||||
</VBox>
|
||||
@@ -1,41 +0,0 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<VBox fx:id="root" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<Label text="%settings.login.header" styleClass="settings-header-label" />
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.login.name" />
|
||||
<ComboBox fx:id="nameComboBox" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.login.address" />
|
||||
<TextField fx:id="addressTextField" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.login.port" />
|
||||
<TextField fx:id="portTextField" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.login.username" />
|
||||
<TextField fx:id="usernameTextField" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.login.password" />
|
||||
<PasswordField fx:id="passwordField" />
|
||||
<Region prefWidth="10" />
|
||||
<ToggleButton fx:id="hidePasswordButton" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.login.updateServer" />
|
||||
<ComboBox fx:id="updateServerComboBox" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.login.verify_server" />
|
||||
<CheckBox fx:id="verifyServerCheckBox" />
|
||||
<Region prefWidth="10" />
|
||||
<Label text="%settings.login.verify_server_disclaimer" style="-fx-font-size: 10px;" maxWidth="Infinity" HBox.hgrow="ALWAYS" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.login.enable_encryption" />
|
||||
<CheckBox fx:id="enableEncryptionCheckBox" />
|
||||
</HBox>
|
||||
</VBox>
|
||||
@@ -1,31 +0,0 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<VBox fx:id="root" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<Label text="%settings.update.header" styleClass="settings-header-label" />
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.update.name" />
|
||||
<ComboBox fx:id="nameComboBox" />
|
||||
<Region prefWidth="10" />
|
||||
<Button fx:id="scanButton" text="%settings.update.scan" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.update.address" />
|
||||
<TextField fx:id="addressTextField" />
|
||||
<Region prefWidth="10" />
|
||||
<Button fx:id="clientOptionsButton" text="%settings.update.clientOptions"/>
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.update.port" />
|
||||
<TextField fx:id="portTextField" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.update.basePath" />
|
||||
<TextField fx:id="basePathTextField" />
|
||||
</HBox>
|
||||
<HBox styleClass="settings-row">
|
||||
<Label text="%settings.update.localPath" />
|
||||
<TextField fx:id="localPathTextField" disable="true" />
|
||||
<Region prefWidth="10" />
|
||||
<Button fx:id="localPathSelectionButton" styleClass="pathSelection" />
|
||||
</HBox>
|
||||
</VBox>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 53 KiB |
BIN
src/main/resources/graphics/ProjectSWGOld.png
Normal file
BIN
src/main/resources/graphics/ProjectSWGOld.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
src/main/resources/graphics/large_load_elbow_grease.png
Normal file
BIN
src/main/resources/graphics/large_load_elbow_grease.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 402 KiB |
@@ -16,7 +16,7 @@ servers.play.cancel=Cancel
|
||||
|
||||
servers.status.unknown=
|
||||
servers.status.scanning=Scanning...
|
||||
servers.status.requires_download=Requires Download
|
||||
servers.status.requires_download=Needs Update
|
||||
servers.status.downloading=Downloading...
|
||||
servers.status.ready=Ready
|
||||
|
||||
@@ -27,7 +27,8 @@ servers.action_info.progress=complete
|
||||
servers.action_info.required=required
|
||||
servers.action_info.downloading=Downloading...
|
||||
|
||||
servers.login.form.title=Login
|
||||
servers.login.feed.title=Website Feed
|
||||
servers.login.form.title=Game
|
||||
servers.login.form.username=Username
|
||||
servers.login.form.password=Password
|
||||
servers.login.form.submit=Login
|
||||
@@ -35,7 +36,7 @@ servers.login.form.submit=Login
|
||||
servers.login.buttons.website=Website
|
||||
servers.login.buttons.create_account=Create Account
|
||||
servers.login.buttons.configuration=Configuration
|
||||
servers.login.buttons.server_list=Server List
|
||||
servers.login.buttons.client_options=Client Options
|
||||
servers.login.launcher_version=Launcher Version
|
||||
|
||||
settings.general.header=General
|
||||
@@ -46,25 +47,14 @@ settings.general.wine=Wine
|
||||
settings.general.admin=Admin Commands
|
||||
settings.general.admin_disclaimer=(This does not grant admin rights on the server)
|
||||
|
||||
settings.login.header=Login Servers
|
||||
settings.login.name=Name
|
||||
settings.login.address=Address
|
||||
settings.login.port=Port
|
||||
settings.login.username=Username
|
||||
settings.login.password=Password
|
||||
settings.login.updateServer=Update Server
|
||||
settings.login.verify_server=SSL Verification
|
||||
settings.login.enable_encryption=TCP Encryption
|
||||
settings.login.verify_server_disclaimer=(This is an important security feature and should be enabled at all times)
|
||||
settings.login.header=Local Server
|
||||
settings.login.local_connection_url=Connection URL
|
||||
settings.login.update_server=Update Server
|
||||
|
||||
settings.update.header=Update Servers
|
||||
settings.update.name=Name
|
||||
settings.update.header=Update Server
|
||||
settings.update.localPath=Local Path
|
||||
settings.update.scan=Scan
|
||||
settings.update.clientOptions=Client Options
|
||||
settings.update.address=Address
|
||||
settings.update.port=Port
|
||||
settings.update.basePath=Base Path
|
||||
settings.update.localPath=Local Path
|
||||
|
||||
settings.forwarder.header=Forwarder
|
||||
settings.forwarder.sendInterval=Packet Interval
|
||||
|
||||
@@ -16,7 +16,7 @@ servers.play.cancel=Abbruch
|
||||
|
||||
servers.status.unknown=Unbekannt
|
||||
servers.status.scanning=Scanne...
|
||||
servers.status.requires_download=Benötigt Download
|
||||
servers.status.requires_download=Ben. Update
|
||||
servers.status.downloading=Downloade...
|
||||
servers.status.ready=Fertig
|
||||
|
||||
@@ -27,6 +27,7 @@ servers.action_info.progress=fertig
|
||||
servers.action_info.required=benötigt
|
||||
servers.action_info.downloading=Lade...
|
||||
|
||||
servers.login.feed.title=Website Feed
|
||||
servers.login.form.title=Anmelden
|
||||
servers.login.form.username=Benutzername
|
||||
servers.login.form.password=Passwort
|
||||
@@ -35,7 +36,7 @@ servers.login.form.submit=Login
|
||||
servers.login.buttons.website=Webseite
|
||||
servers.login.buttons.create_account=Konto erstellen
|
||||
servers.login.buttons.configuration=Konfiguration
|
||||
servers.login.buttons.server_list=Serverliste
|
||||
servers.login.buttons.client_options=Client-Optionen
|
||||
servers.login.launcher_version=Launcher Version
|
||||
|
||||
settings.general.header=Allgemein
|
||||
@@ -44,21 +45,11 @@ settings.general.theme=Stil
|
||||
settings.general.locale=Sprache
|
||||
settings.general.wine=Wine
|
||||
|
||||
settings.login.header=Login Servers
|
||||
settings.login.name=Name
|
||||
settings.login.address=Adresse
|
||||
settings.login.port=Port
|
||||
settings.login.username=Nutzername
|
||||
settings.login.password=Passwort
|
||||
settings.login.updateServer=Update Server
|
||||
settings.login.verify_server=SSL Verification
|
||||
settings.login.verify_server_disclaimer=(This is an important security feature and should be enabled at all times)
|
||||
settings.login.header=Lokal Servers
|
||||
settings.login.local_connection_url=Verbindungs URL
|
||||
settings.login.update_server=Update Server
|
||||
|
||||
settings.update.header=Update Servers
|
||||
settings.update.name=Name
|
||||
settings.update.localPath=Lokaler Pfad
|
||||
settings.update.scan=Scannen
|
||||
settings.update.clientOptions=Client-Optionen
|
||||
settings.update.address=Adresse
|
||||
settings.update.port=Port
|
||||
settings.update.basePath=Basis Pfad
|
||||
settings.update.localPath=Lokaler Pfad
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (C) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* This file is part of the ProjectSWG Launcher. *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.launcher.utility;
|
||||
|
||||
import me.joshlarson.json.JSONArray;
|
||||
import me.joshlarson.json.JSONObject;
|
||||
import me.joshlarson.json.JSONOutputStream;
|
||||
import net.openhft.hashing.LongHashFunction;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileChannel.MapMode;
|
||||
import java.util.Objects;
|
||||
import java.util.zip.Adler32;
|
||||
|
||||
public class CreateUpdateList {
|
||||
|
||||
public static void main(String [] args) throws IOException {
|
||||
if (args.length <= 0) {
|
||||
System.err.println("Invalid arguments. Expected: java -jar CreateUpdateList.jar <patch directory>");
|
||||
return;
|
||||
}
|
||||
File patch = new File(args[0]);
|
||||
if (!patch.isDirectory()) {
|
||||
System.err.println("Invalid patch directory - not a directory: " + patch);
|
||||
return;
|
||||
}
|
||||
patch = patch.getCanonicalFile();
|
||||
|
||||
System.out.println("Opening " + patch + " for reading...");
|
||||
JSONArray files = new JSONArray();
|
||||
createFileList(files, patch, patch.getAbsolutePath());
|
||||
|
||||
System.out.println("Saving to file...");
|
||||
try (JSONOutputStream out = new JSONOutputStream(new FileOutputStream(new File("files.json")))) {
|
||||
out.writeArray(files);
|
||||
}
|
||||
System.out.println("Done.");
|
||||
}
|
||||
|
||||
private static void createFileList(JSONArray files, File directory, String filter) {
|
||||
for (File child : Objects.requireNonNull(directory.listFiles())) {
|
||||
if (child.isFile()) {
|
||||
addFile(files, child, filter);
|
||||
} else if (child.isDirectory()) {
|
||||
createFileList(files, child, filter);
|
||||
} else {
|
||||
System.err.println("Unknown file: " + child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFile(JSONArray files, File file, String filter) {
|
||||
String path = file.getAbsolutePath().substring(filter.length());
|
||||
if (!isValidFile(file)) {
|
||||
System.out.println(" Ignoring " + path);
|
||||
return;
|
||||
}
|
||||
System.out.println(" Adding " + path);
|
||||
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("path", path);
|
||||
obj.put("length", file.length());
|
||||
try (FileChannel fc = FileChannel.open(file.toPath())) {
|
||||
ByteBuffer bb = fc.map(MapMode.READ_ONLY, 0, file.length());
|
||||
obj.put("adler32", getAdler32(bb));
|
||||
obj.put("xxhash", getXXHash(bb));
|
||||
// obj.put("md5", getMD5(bb));
|
||||
// obj.put("sha3", getSHA3(bb));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
files.add(obj);
|
||||
}
|
||||
|
||||
private static long getAdler32(ByteBuffer bb) {
|
||||
bb.position(0);
|
||||
Adler32 adler = new Adler32();
|
||||
adler.update(bb);
|
||||
return adler.getValue();
|
||||
}
|
||||
|
||||
private static long getXXHash(ByteBuffer bb) {
|
||||
bb.position(0);
|
||||
return LongHashFunction.xx().hashBytes(bb);
|
||||
}
|
||||
|
||||
// private static String getMD5(ByteBuffer bb) {
|
||||
// try {
|
||||
// bb.position(0);
|
||||
// MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
// digest.update(bb);
|
||||
// return Hex.toHexString(digest.digest());
|
||||
// } catch (NoSuchAlgorithmException e) {
|
||||
// return "";
|
||||
// }
|
||||
// }
|
||||
|
||||
// private static String getSHA3(ByteBuffer bb) {
|
||||
// bb.position(0);
|
||||
// MessageDigest digest = new SHA3.Digest512();
|
||||
// digest.update(bb);
|
||||
// return Hex.toHexString(digest.digest());
|
||||
// }
|
||||
|
||||
private static boolean isValidFile(File file) {
|
||||
String name = file.getName();
|
||||
return !name.equals("files.json") && !name.equals("user.cfg") && !name.equals("options.cfg") && !name.endsWith(".log") && !name.endsWith(".iff");
|
||||
}
|
||||
|
||||
}
|
||||
Submodule zero_allocation_hashing deleted from f531d2a7f5
Reference in New Issue
Block a user