diff --git a/.gitmodules b/.gitmodules index f7777a2..6284747 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/build.gradle.kts b/build.gradle.kts index 74ebbc2..c26aa1a 100644 --- a/build.gradle.kts +++ b/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("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("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("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().configureEach { kotlinOptions { jvmTarget = kotlinTargetJdk } - destinationDir = sourceSets.main.get().java.outputDir + destinationDirectory.set(File(destinationDirectory.get().asFile.path.replace("kotlin", "java"))) } diff --git a/client-holocore b/client-holocore deleted file mode 160000 index 242066c..0000000 --- a/client-holocore +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 242066cbb54beffebd84262d407e99c21c6aa413 diff --git a/forwarder b/forwarder index 99b81aa..f892d4b 160000 --- a/forwarder +++ b/forwarder @@ -1 +1 @@ -Subproject commit 99b81aa05f97fe77a3ea13115b32ad76b7815dd0 +Subproject commit f892d4beea24afd4827b0736edf2a519980ee682 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c..41d9927 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f371643..aa991fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/gradlew b/gradlew index 4f906e0..1b6c787 100755 --- a/gradlew +++ b/gradlew @@ -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" "$@" diff --git a/pswgcommon b/pswgcommon index c60ac9f..d23749c 160000 --- a/pswgcommon +++ b/pswgcommon @@ -1 +1 @@ -Subproject commit c60ac9f00c0c33e9e00d46dfc4f679a2669d8bfa +Subproject commit d23749c5a0c96f39a3bd9cedd77010896d071811 diff --git a/settings.gradle.kts b/settings.gradle.kts index 9de2352..0d88d72 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,2 @@ rootProject.name = "launcher" -include("pswgcommon", "client-holocore", "forwarder", "tornadofx", "zero_allocation_hashing") +include("pswgcommon", "forwarder", "tornadofx") diff --git a/src/main/java/com/projectswg/launcher/core/Launcher.kt b/src/main/java/com/projectswg/launcher/Launcher.kt similarity index 67% rename from src/main/java/com/projectswg/launcher/core/Launcher.kt rename to src/main/java/com/projectswg/launcher/Launcher.kt index b46deb2..b25997e 100644 --- a/src/main/java/com/projectswg/launcher/core/Launcher.kt +++ b/src/main/java/com/projectswg/launcher/Launcher.kt @@ -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 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 } } diff --git a/src/main/java/com/projectswg/launcher/core/Test.kt b/src/main/java/com/projectswg/launcher/Test.kt similarity index 92% rename from src/main/java/com/projectswg/launcher/core/Test.kt rename to src/main/java/com/projectswg/launcher/Test.kt index c0436f2..625cc94 100644 --- a/src/main/java/com/projectswg/launcher/core/Test.kt +++ b/src/main/java/com/projectswg/launcher/Test.kt @@ -17,9 +17,9 @@ * along with this program. If not, see //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 diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/LauncherData.java b/src/main/java/com/projectswg/launcher/core/resources/data/LauncherData.java deleted file mode 100644 index 63511eb..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/data/LauncherData.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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; - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/announcements/CardData.kt b/src/main/java/com/projectswg/launcher/core/resources/data/announcements/CardData.kt deleted file mode 100644 index ffa34a9..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/data/announcements/CardData.kt +++ /dev/null @@ -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?) diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/general/GeneralData.java b/src/main/java/com/projectswg/launcher/core/resources/data/general/GeneralData.java deleted file mode 100644 index 3fbc93c..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/data/general/GeneralData.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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 theme; - private final ConcurrentReference 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 getThemeProperty() { - return theme; - } - - @NotNull - public ConcurrentReference 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); - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/general/LauncherTheme.java b/src/main/java/com/projectswg/launcher/core/resources/data/general/LauncherTheme.java deleted file mode 100644 index 7168106..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/data/general/LauncherTheme.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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 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); - } -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/login/LoginServerInstanceInfo.java b/src/main/java/com/projectswg/launcher/core/resources/data/login/LoginServerInstanceInfo.java deleted file mode 100644 index 65b1910..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/data/login/LoginServerInstanceInfo.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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); - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/update/UpdateData.java b/src/main/java/com/projectswg/launcher/core/resources/data/update/UpdateData.java deleted file mode 100644 index 7f1c99e..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/data/update/UpdateData.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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 servers; - - public UpdateData() { - this.servers = new ConcurrentSet<>(new CopyOnWriteArraySet<>()); - } - - @NotNull - public ConcurrentSet getServers() { - return servers; - } - - public void addServer(@NotNull UpdateServer server) { - servers.add(server); - } - - public void removeServer(@NotNull UpdateServer server) { - servers.remove(server); - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/Card.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/Card.kt deleted file mode 100644 index f8cd583..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/Card.kt +++ /dev/null @@ -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 //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()) - } - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/CardContainer.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/CardContainer.kt deleted file mode 100644 index c0d2803..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/CardContainer.kt +++ /dev/null @@ -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 //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 by param() - - init { - root.bindChildren(children) { - val card = find(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() - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/ServerListView.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/ServerListView.kt deleted file mode 100644 index 2718784..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/ServerListView.kt +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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 { - items = LauncherData.INSTANCE.login.serversProperty - isFocusTraversable = false - placeholder = label(messages["noServers"]) - setSortPolicy { _ -> Comparator.comparing { it.name }; true } - - column(messages["servers.column.name"], valueProvider={cellDataFeatures:TableColumn.CellDataFeatures -> ReadOnlyStringWrapper(cellDataFeatures.value?.name) }).apply { - prefWidth = COL_WIDTH_LARGE - styleClass += "center-table-cell" - } - column(messages["servers.column.gameVersion"], valueProvider={cellDataFeatures:TableColumn.CellDataFeatures -> 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 -> 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 -> 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 - - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/events/Events.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/events/Events.kt deleted file mode 100644 index 0e04c47..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/events/Events.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.projectswg.launcher.core.resources.gui.events - -import tornadofx.FXEvent - -object LauncherClosingEvent : FXEvent() diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/servers/ServerPlayCell.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/servers/ServerPlayCell.kt deleted file mode 100644 index 07ad9a7..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/servers/ServerPlayCell.kt +++ /dev/null @@ -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 //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() { - - 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::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): 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]) - } - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsForwarderView.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsForwarderView.kt deleted file mode 100644 index a1fa17f..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsForwarderView.kt +++ /dev/null @@ -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 //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() - } - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsGeneralView.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsGeneralView.kt deleted file mode 100644 index 3e51f29..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsGeneralView.kt +++ /dev/null @@ -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 //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 by fxid() - private val localeComboBox: ComboBox 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 - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsLoginView.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsLoginView.kt deleted file mode 100644 index e0510b3..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsLoginView.kt +++ /dev/null @@ -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 //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 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 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 { - 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 - - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsUpdateView.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsUpdateView.kt deleted file mode 100644 index 13cb608..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/settings/SettingsUpdateView.kt +++ /dev/null @@ -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 //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 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 - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/style/CardStyle.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/style/CardStyle.kt deleted file mode 100644 index 470d515..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/style/CardStyle.kt +++ /dev/null @@ -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 - } - } -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/style/LauncherStyle.kt b/src/main/java/com/projectswg/launcher/core/resources/gui/style/LauncherStyle.kt deleted file mode 100644 index 1734d90..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/style/LauncherStyle.kt +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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") - } - } - } - } - } -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/pipeline/Pipeline.java b/src/main/java/com/projectswg/launcher/core/resources/pipeline/Pipeline.java deleted file mode 100644 index 9830a04..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/pipeline/Pipeline.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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 PipelineCompiler compile(String name) { - return new PipelineCompiler<>(name); - } - - public static PipelineExecutor execute(String name) { - return new PipelineExecutor<>(name); - } - - public static class PipelineCompiler { - - private final List> stages; - private final String name; - - public PipelineCompiler(String name) { - this.stages = new CopyOnWriteArrayList<>(); - this.name = name; - } - - public PipelineCompiler next(Predicate stage) { - stages.add(stage); - return this; - } - - public PipelineCompiler next(Consumer stage) { - return next(in -> {stage.accept(in); return true; }); - } - - public PipelineCompiler next(Runnable stage) { - return next(in -> { stage.run(); return true; }); - } - - public void execute(T input) { - int stageIndex = 0; - try { - for (Predicate 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 { - - private final String name; - private boolean terminated; - - public PipelineExecutor() { - this(""); - } - - public PipelineExecutor(String name) { - this.name = name; - this.terminated = false; - } - - public PipelineExecutor execute(Predicate 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 execute(Consumer 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 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; - } - - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/resources/pipeline/UpdateServerUpdater.java b/src/main/java/com/projectswg/launcher/core/resources/pipeline/UpdateServerUpdater.java deleted file mode 100644 index 37e9637..0000000 --- a/src/main/java/com/projectswg/launcher/core/resources/pipeline/UpdateServerUpdater.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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 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 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 serverList = info.getServer().getRequiredFiles(); - List 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 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 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 files) { - this.files = files; - } - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/services/data/AnnouncementService.java b/src/main/java/com/projectswg/launcher/core/services/data/AnnouncementService.java deleted file mode 100644 index 5ab4e7e..0000000 --- a/src/main/java/com/projectswg/launcher/core/services/data/AnnouncementService.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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 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 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 announcements = updateAnnouncements(); - if (announcements == null) - return; - update(announcements); - } - - private void update(Map announcements) { - List announcementCards = parseCards(announcements.get("announcements")).stream().map(this::downloadImage).collect(Collectors.toList()); - List 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 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 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 var : VARIABLES.entrySet()) { - str = str.replaceAll(var.getKey(), var.getValue()); - } - return str; - } - - private static boolean passesVersionCheck(String specifiedVersionStr, BiPredicate 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 updateAnnouncements() { - File localFileList = new File(LocalUtilities.getApplicationDirectory(), "announcements.json"); - - Log.t("Retrieving latest announcements..."); - Map 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; - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/services/data/PreferencesDataService.java b/src/main/java/com/projectswg/launcher/core/services/data/PreferencesDataService.java deleted file mode 100644 index 7a35288..0000000 --- a/src/main/java/com/projectswg/launcher/core/services/data/PreferencesDataService.java +++ /dev/null @@ -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 . * - * * - ***********************************************************************************/ - -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 void ifPresent(Preferences p, String key, Function transform, Consumer setter) { - String val = p.get(key, null); - if (val != null) - setter.accept(transform.apply(val)); - } - - private static void ifPresent(Preferences p, String key, Consumer 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; - } - -} diff --git a/src/main/java/com/projectswg/launcher/core/services/data/RemoteDataService.kt b/src/main/java/com/projectswg/launcher/core/services/data/RemoteDataService.kt deleted file mode 100644 index b521d0f..0000000 --- a/src/main/java/com/projectswg/launcher/core/services/data/RemoteDataService.kt +++ /dev/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 //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 - 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, 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 - } - -} diff --git a/src/main/java/com/projectswg/launcher/resources/data/LauncherData.kt b/src/main/java/com/projectswg/launcher/resources/data/LauncherData.kt new file mode 100644 index 0000000..559a09f --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/data/LauncherData.kt @@ -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 //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" + } + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/announcements/WebsitePostFeed.kt b/src/main/java/com/projectswg/launcher/resources/data/announcements/WebsitePostFeed.kt similarity index 94% rename from src/main/java/com/projectswg/launcher/core/resources/data/announcements/WebsitePostFeed.kt rename to src/main/java/com/projectswg/launcher/resources/data/announcements/WebsitePostFeed.kt index 9c7bbc4..cad23f2 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/data/announcements/WebsitePostFeed.kt +++ b/src/main/java/com/projectswg/launcher/resources/data/announcements/WebsitePostFeed.kt @@ -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 } } diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/announcements/WebsitePostMessage.kt b/src/main/java/com/projectswg/launcher/resources/data/announcements/WebsitePostMessage.kt similarity index 74% rename from src/main/java/com/projectswg/launcher/core/resources/data/announcements/WebsitePostMessage.kt rename to src/main/java/com/projectswg/launcher/resources/data/announcements/WebsitePostMessage.kt index f44bda8..bdf2724 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/data/announcements/WebsitePostMessage.kt +++ b/src/main/java/com/projectswg/launcher/resources/data/announcements/WebsitePostMessage.kt @@ -1,4 +1,4 @@ -package com.projectswg.launcher.core.resources.data.announcements +package com.projectswg.launcher.resources.data.announcements import java.util.Date diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/announcements/WebsitePostMessageImage.kt b/src/main/java/com/projectswg/launcher/resources/data/announcements/WebsitePostMessageImage.kt similarity index 96% rename from src/main/java/com/projectswg/launcher/core/resources/data/announcements/WebsitePostMessageImage.kt rename to src/main/java/com/projectswg/launcher/resources/data/announcements/WebsitePostMessageImage.kt index 2a1c718..932d337 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/data/announcements/WebsitePostMessageImage.kt +++ b/src/main/java/com/projectswg/launcher/resources/data/announcements/WebsitePostMessageImage.kt @@ -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"), diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/forwarder/ForwarderData.kt b/src/main/java/com/projectswg/launcher/resources/data/forwarder/ForwarderData.kt similarity index 96% rename from src/main/java/com/projectswg/launcher/core/resources/data/forwarder/ForwarderData.kt rename to src/main/java/com/projectswg/launcher/resources/data/forwarder/ForwarderData.kt index 0440597..5ac0155 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/data/forwarder/ForwarderData.kt +++ b/src/main/java/com/projectswg/launcher/resources/data/forwarder/ForwarderData.kt @@ -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 diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/announcements/AnnouncementsData.kt b/src/main/java/com/projectswg/launcher/resources/data/general/GeneralData.kt similarity index 60% rename from src/main/java/com/projectswg/launcher/core/resources/data/announcements/AnnouncementsData.kt rename to src/main/java/com/projectswg/launcher/resources/data/general/GeneralData.kt index e596357..38be4d2 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/data/announcements/AnnouncementsData.kt +++ b/src/main/java/com/projectswg/launcher/resources/data/general/GeneralData.kt @@ -17,23 +17,25 @@ * along with this program. If not, see //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() - val announcementCardsProperty: ObservableList = FXCollections.observableArrayList() - val serverListCards = ConcurrentList() - val serverListCardsProperty: ObservableList = 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 -} +} \ No newline at end of file diff --git a/src/main/java/com/projectswg/launcher/resources/data/login/AuthenticationData.kt b/src/main/java/com/projectswg/launcher/resources/data/login/AuthenticationData.kt new file mode 100644 index 0000000..4691a92 --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/data/login/AuthenticationData.kt @@ -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 + +} diff --git a/src/main/java/com/projectswg/launcher/resources/data/login/LoginData.kt b/src/main/java/com/projectswg/launcher/resources/data/login/LoginData.kt new file mode 100644 index 0000000..d37093a --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/data/login/LoginData.kt @@ -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 //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(CopyOnWriteArraySet()) + val authenticationProperty: ObservableList = FXCollections.observableArrayList() + + val servers = ConcurrentSet(CopyOnWriteArraySet()) + val serversProperty: ObservableList = FXCollections.observableArrayList() + + val activeServerProperty = SimpleObjectProperty() + val lastSelectedServerProperty = SimpleStringProperty() + + val localServerProperty = SimpleObjectProperty() + + 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 + } + +} diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/login/LoginServer.kt b/src/main/java/com/projectswg/launcher/resources/data/login/LoginServer.kt similarity index 77% rename from src/main/java/com/projectswg/launcher/core/resources/data/login/LoginServer.kt rename to src/main/java/com/projectswg/launcher/resources/data/login/LoginServer.kt index 507fe34..cdd5620 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/data/login/LoginServer.kt +++ b/src/main/java/com/projectswg/launcher/resources/data/login/LoginServer.kt @@ -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(null) val updateServerProperty = SimpleObjectProperty(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) } diff --git a/src/main/java/com/projectswg/launcher/core/services/data/DataManager.java b/src/main/java/com/projectswg/launcher/resources/data/login/LoginServerInstanceInfo.kt similarity index 51% rename from src/main/java/com/projectswg/launcher/core/services/data/DataManager.java rename to src/main/java/com/projectswg/launcher/resources/data/login/LoginServerInstanceInfo.kt index b654243..0285d12 100644 --- a/src/main/java/com/projectswg/launcher/core/services/data/DataManager.java +++ b/src/main/java/com/projectswg/launcher/resources/data/login/LoginServerInstanceInfo.kt @@ -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 . * - * * - ***********************************************************************************/ + * along with this program. If not, see //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 + +} \ No newline at end of file diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/login/LoginData.kt b/src/main/java/com/projectswg/launcher/resources/data/update/UpdateData.kt similarity index 70% rename from src/main/java/com/projectswg/launcher/core/resources/data/login/LoginData.kt rename to src/main/java/com/projectswg/launcher/resources/data/update/UpdateData.kt index 5bb13f5..f02928b 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/data/login/LoginData.kt +++ b/src/main/java/com/projectswg/launcher/resources/data/update/UpdateData.kt @@ -17,30 +17,35 @@ * along with this program. If not, see //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(CopyOnWriteArraySet()) - val serversProperty: ObservableList = FXCollections.observableArrayList() + val servers: ConcurrentSet = ConcurrentSet(CopyOnWriteArraySet()) + val serversProperty: ObservableList = 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) - } - -} +} \ No newline at end of file diff --git a/src/main/java/com/projectswg/launcher/core/resources/data/update/UpdateServer.kt b/src/main/java/com/projectswg/launcher/resources/data/update/UpdateServer.kt similarity index 82% rename from src/main/java/com/projectswg/launcher/core/resources/data/update/UpdateServer.kt rename to src/main/java/com/projectswg/launcher/resources/data/update/UpdateServer.kt index e5a09af..d5df98a 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/data/update/UpdateServer.kt +++ b/src/main/java/com/projectswg/launcher/resources/data/update/UpdateServer.kt @@ -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 = 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"), diff --git a/src/main/java/com/projectswg/launcher/core/resources/game/GameInstance.kt b/src/main/java/com/projectswg/launcher/resources/game/GameInstance.kt similarity index 82% rename from src/main/java/com/projectswg/launcher/core/resources/game/GameInstance.kt rename to src/main/java/com/projectswg/launcher/resources/game/GameInstance.kt index 4057838..8e69831 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/game/GameInstance.kt +++ b/src/main/java/com/projectswg/launcher/resources/game/GameInstance.kt @@ -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() } diff --git a/src/main/java/com/projectswg/launcher/core/resources/game/ProcessExecutor.java b/src/main/java/com/projectswg/launcher/resources/game/ProcessExecutor.java similarity index 92% rename from src/main/java/com/projectswg/launcher/core/resources/game/ProcessExecutor.java rename to src/main/java/com/projectswg/launcher/resources/game/ProcessExecutor.java index a7f9fdf..f18d173 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/game/ProcessExecutor.java +++ b/src/main/java/com/projectswg/launcher/resources/game/ProcessExecutor.java @@ -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(); }); } diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/FontAwesomeUtilities.kt b/src/main/java/com/projectswg/launcher/resources/gui/FontAwesomeUtilities.kt similarity index 87% rename from src/main/java/com/projectswg/launcher/core/resources/gui/FontAwesomeUtilities.kt rename to src/main/java/com/projectswg/launcher/resources/gui/FontAwesomeUtilities.kt index 35aa681..1bf0382 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/FontAwesomeUtilities.kt +++ b/src/main/java/com/projectswg/launcher/resources/gui/FontAwesomeUtilities.kt @@ -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 diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/NavigationView.kt b/src/main/java/com/projectswg/launcher/resources/gui/NavigationView.kt similarity index 78% rename from src/main/java/com/projectswg/launcher/core/resources/gui/NavigationView.kt rename to src/main/java/com/projectswg/launcher/resources/gui/NavigationView.kt index 2df5dc5..fcd7058 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/NavigationView.kt +++ b/src/main/java/com/projectswg/launcher/resources/gui/NavigationView.kt @@ -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 = 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().root } } - - group { - AnchorPane.setBottomAnchor(this, 15.0) - AnchorPane.setLeftAnchor(this, 5.0) - - label(selectedTab) { - rotate = -90.0 - font = Font(28.0) - } - } } } diff --git a/src/main/java/com/projectswg/launcher/resources/gui/ServerListView.kt b/src/main/java/com/projectswg/launcher/resources/gui/ServerListView.kt new file mode 100644 index 0000000..26b91cd --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/gui/ServerListView.kt @@ -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 . * + * * + ***********************************************************************************/ + +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(times = 1) { + find().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 + } + } + } + } + +} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/SettingsView.kt b/src/main/java/com/projectswg/launcher/resources/gui/SettingsView.kt similarity index 75% rename from src/main/java/com/projectswg/launcher/core/resources/gui/SettingsView.kt rename to src/main/java/com/projectswg/launcher/resources/gui/SettingsView.kt index 312a594..5e14740 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/SettingsView.kt +++ b/src/main/java/com/projectswg/launcher/resources/gui/SettingsView.kt @@ -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().root) separator() diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/admin/AdminDisplay.kt b/src/main/java/com/projectswg/launcher/resources/gui/admin/AdminDisplay.kt similarity index 98% rename from src/main/java/com/projectswg/launcher/core/resources/gui/admin/AdminDisplay.kt rename to src/main/java/com/projectswg/launcher/resources/gui/admin/AdminDisplay.kt index 09a3e18..ca6e1c6 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/admin/AdminDisplay.kt +++ b/src/main/java/com/projectswg/launcher/resources/gui/admin/AdminDisplay.kt @@ -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 diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/admin/ForwarderPacket.kt b/src/main/java/com/projectswg/launcher/resources/gui/admin/ForwarderPacket.kt similarity index 89% rename from src/main/java/com/projectswg/launcher/core/resources/gui/admin/ForwarderPacket.kt rename to src/main/java/com/projectswg/launcher/resources/gui/admin/ForwarderPacket.kt index 9b1f9be..0cd56c6 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/admin/ForwarderPacket.kt +++ b/src/main/java/com/projectswg/launcher/resources/gui/admin/ForwarderPacket.kt @@ -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 diff --git a/src/main/java/com/projectswg/launcher/resources/gui/events/Events.kt b/src/main/java/com/projectswg/launcher/resources/gui/events/Events.kt new file mode 100644 index 0000000..f425f78 --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/gui/events/Events.kt @@ -0,0 +1,6 @@ +package com.projectswg.launcher.resources.gui.events + +import tornadofx.FXEvent + +object LauncherClosingEvent : FXEvent() +object LauncherNewVersionEvent : FXEvent() diff --git a/src/main/java/com/projectswg/launcher/resources/gui/servers/LauncherUpdatePopup.kt b/src/main/java/com/projectswg/launcher/resources/gui/servers/LauncherUpdatePopup.kt new file mode 100644 index 0000000..71aeb4e --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/gui/servers/LauncherUpdatePopup.kt @@ -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() + } + } + } + } + +} diff --git a/src/main/java/com/projectswg/launcher/core/resources/gui/servers/WebsitePostFeedList.kt b/src/main/java/com/projectswg/launcher/resources/gui/servers/WebsitePostFeedList.kt similarity index 81% rename from src/main/java/com/projectswg/launcher/core/resources/gui/servers/WebsitePostFeedList.kt rename to src/main/java/com/projectswg/launcher/resources/gui/servers/WebsitePostFeedList.kt index 9f2125f..8177edf 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/gui/servers/WebsitePostFeedList.kt +++ b/src/main/java/com/projectswg/launcher/resources/gui/servers/WebsitePostFeedList.kt @@ -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 { diff --git a/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsForwarderView.kt b/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsForwarderView.kt new file mode 100644 index 0000000..081a597 --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsForwarderView.kt @@ -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 //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) + } + } + } + } + +} diff --git a/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsGeneralView.kt b/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsGeneralView.kt new file mode 100644 index 0000000..ae81461 --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsGeneralView.kt @@ -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 //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 + } + +} diff --git a/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsLoginView.kt b/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsLoginView.kt new file mode 100644 index 0000000..f8602fc --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsLoginView.kt @@ -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 //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 { + items = LauncherData.INSTANCE.update.serversProperty + valueProperty().bindBidirectional(localServer.updateServerProperty) + } + } + } + +} diff --git a/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsUpdateView.kt b/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsUpdateView.kt new file mode 100644 index 0000000..0a019b0 --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/gui/settings/SettingsUpdateView.kt @@ -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 //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 + } + +} diff --git a/src/main/java/com/projectswg/launcher/resources/gui/style/LauncherStyle.kt b/src/main/java/com/projectswg/launcher/resources/gui/style/LauncherStyle.kt new file mode 100644 index 0000000..fecfe54 --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/gui/style/LauncherStyle.kt @@ -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 . * + * * + ***********************************************************************************/ + +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 + } + } + } + +} diff --git a/src/main/java/com/projectswg/launcher/core/resources/intents/Game.kt b/src/main/java/com/projectswg/launcher/resources/intents/Game.kt similarity index 50% rename from src/main/java/com/projectswg/launcher/core/resources/intents/Game.kt rename to src/main/java/com/projectswg/launcher/resources/intents/Game.kt index 492f234..575465f 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/intents/Game.kt +++ b/src/main/java/com/projectswg/launcher/resources/intents/Game.kt @@ -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() diff --git a/src/main/java/com/projectswg/launcher/core/resources/intents/Updates.kt b/src/main/java/com/projectswg/launcher/resources/intents/Updates.kt similarity index 61% rename from src/main/java/com/projectswg/launcher/core/resources/intents/Updates.kt rename to src/main/java/com/projectswg/launcher/resources/intents/Updates.kt index c4d6e82..48f230c 100644 --- a/src/main/java/com/projectswg/launcher/core/resources/intents/Updates.kt +++ b/src/main/java/com/projectswg/launcher/resources/intents/Updates.kt @@ -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() diff --git a/src/main/java/com/projectswg/launcher/resources/pipeline/LauncherConfigurationUpdater.kt b/src/main/java/com/projectswg/launcher/resources/pipeline/LauncherConfigurationUpdater.kt new file mode 100644 index 0000000..6828399 --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/pipeline/LauncherConfigurationUpdater.kt @@ -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(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 ?: 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" + } + +} diff --git a/src/main/java/com/projectswg/launcher/resources/pipeline/UpdateServerUpdater.kt b/src/main/java/com/projectswg/launcher/resources/pipeline/UpdateServerUpdater.kt new file mode 100644 index 0000000..afd8700 --- /dev/null +++ b/src/main/java/com/projectswg/launcher/resources/pipeline/UpdateServerUpdater.kt @@ -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 //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 + 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 ?: 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 = Objects.requireNonNull?>(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 = info.server.requiredFiles + val updateList: List = 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): 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? = null + } +} \ No newline at end of file diff --git a/src/main/java/com/projectswg/launcher/services/data/DataManager.kt b/src/main/java/com/projectswg/launcher/services/data/DataManager.kt new file mode 100644 index 0000000..ddb9ad8 --- /dev/null +++ b/src/main/java/com/projectswg/launcher/services/data/DataManager.kt @@ -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 //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() diff --git a/src/main/java/com/projectswg/launcher/core/services/data/DownloadService.kt b/src/main/java/com/projectswg/launcher/services/data/DownloadService.kt similarity index 91% rename from src/main/java/com/projectswg/launcher/core/services/data/DownloadService.kt rename to src/main/java/com/projectswg/launcher/services/data/DownloadService.kt index 8a7c476..283ed0e 100644 --- a/src/main/java/com/projectswg/launcher/core/services/data/DownloadService.kt +++ b/src/main/java/com/projectswg/launcher/services/data/DownloadService.kt @@ -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 diff --git a/src/main/java/com/projectswg/launcher/services/data/PreferencesDataService.kt b/src/main/java/com/projectswg/launcher/services/data/PreferencesDataService.kt new file mode 100644 index 0000000..b16b0da --- /dev/null +++ b/src/main/java/com/projectswg/launcher/services/data/PreferencesDataService.kt @@ -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 //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 + + 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) { + 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) { + LauncherData.INSTANCE.update.localPath = settings.getString("local_path", "") + } + + private fun loadSettingsLogin(settings: Map) { + 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) { + 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) { + 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>) { + 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>) { + 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): 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 { + @Suppress("UNCHECKED_CAST") + return obj as? Map ?: mapOf() + } + + private fun castListOfMap(obj: Any?): List> { + @Suppress("UNCHECKED_CAST") + return obj as? List> ?: listOf() + } + + @Synchronized + private fun savePreferences() { + JSONOutputStream(FileOutputStream(File(LauncherData.getApplicationDataDirectory(), "settings.json"))).use { + it.writeObject(jsonSettings()) + } + } + + private fun jsonSettings(): Map { + return mapOf( + "general" to jsonSettingsGeneral(), + "update" to jsonSettingsUpdate(), + "login" to jsonSettingsLogin(), + "forwarder" to jsonSettingsForwarder() + ) + } + + private fun jsonSettingsGeneral(): Map { + 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 { + return mapOf( + "local_path" to LauncherData.INSTANCE.update.localPath + ) + } + + private fun jsonSettingsLogin(): Map { + 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 { + val authentications = HashMap() + + 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 { + return mapOf( + "send_interval" to LauncherData.INSTANCE.forwarder.sendInterval, + "send_max" to LauncherData.INSTANCE.forwarder.sendMax + ) + } + + private fun Map?.getMap(key: String): Map { + return castMap(this?.getOrDefault(key, mapOf())) + } + + private fun Map?.getListOfMap(key: String): List> { + return castListOfMap(this?.getOrDefault(key, listOf>())) + } + + private fun Map?.getString(key: String, defaultValue: String): String { + return (this?.getOrDefault(key, defaultValue) as? String?) ?: defaultValue + } + + private fun Map?.getInt(key: String, defaultValue: Int): Int { + return ((this?.getOrDefault(key, defaultValue) as? Number?) ?: defaultValue).toInt() + } + + private fun Map?.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 + } + } +} \ No newline at end of file diff --git a/src/main/java/com/projectswg/launcher/services/data/RemoteDataService.kt b/src/main/java/com/projectswg/launcher/services/data/RemoteDataService.kt new file mode 100644 index 0000000..e055184 --- /dev/null +++ b/src/main/java/com/projectswg/launcher/services/data/RemoteDataService.kt @@ -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 //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 + } + +} diff --git a/src/main/java/com/projectswg/launcher/core/services/launcher/GameService.kt b/src/main/java/com/projectswg/launcher/services/launcher/GameService.kt similarity index 89% rename from src/main/java/com/projectswg/launcher/core/services/launcher/GameService.kt rename to src/main/java/com/projectswg/launcher/services/launcher/GameService.kt index 859314f..f5bd50f 100644 --- a/src/main/java/com/projectswg/launcher/core/services/launcher/GameService.kt +++ b/src/main/java/com/projectswg/launcher/services/launcher/GameService.kt @@ -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 diff --git a/src/main/java/com/projectswg/launcher/core/services/launcher/LauncherManager.kt b/src/main/java/com/projectswg/launcher/services/launcher/LauncherManager.kt similarity index 96% rename from src/main/java/com/projectswg/launcher/core/services/launcher/LauncherManager.kt rename to src/main/java/com/projectswg/launcher/services/launcher/LauncherManager.kt index ca541a0..24377f3 100644 --- a/src/main/java/com/projectswg/launcher/core/services/launcher/LauncherManager.kt +++ b/src/main/java/com/projectswg/launcher/services/launcher/LauncherManager.kt @@ -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 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 7bac409..2a7f847 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -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; } diff --git a/src/main/resources/com/projectswg/launcher/core/resources/gui/AnnouncementView.fxml b/src/main/resources/com/projectswg/launcher/core/resources/gui/AnnouncementView.fxml deleted file mode 100644 index 453fa31..0000000 --- a/src/main/resources/com/projectswg/launcher/core/resources/gui/AnnouncementView.fxml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/main/resources/com/projectswg/launcher/core/resources/gui/NavigationView.fxml b/src/main/resources/com/projectswg/launcher/core/resources/gui/NavigationView.fxml deleted file mode 100644 index 28e9d51..0000000 --- a/src/main/resources/com/projectswg/launcher/core/resources/gui/NavigationView.fxml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/com/projectswg/launcher/core/resources/gui/ServerListView.fxml b/src/main/resources/com/projectswg/launcher/core/resources/gui/ServerListView.fxml deleted file mode 100644 index 1477139..0000000 --- a/src/main/resources/com/projectswg/launcher/core/resources/gui/ServerListView.fxml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/com/projectswg/launcher/core/resources/gui/SettingsView.fxml b/src/main/resources/com/projectswg/launcher/core/resources/gui/SettingsView.fxml deleted file mode 100644 index 99cd482..0000000 --- a/src/main/resources/com/projectswg/launcher/core/resources/gui/SettingsView.fxml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/main/resources/com/projectswg/launcher/core/resources/gui/settings/SettingsForwarderView.fxml b/src/main/resources/com/projectswg/launcher/core/resources/gui/settings/SettingsForwarderView.fxml deleted file mode 100644 index b696be9..0000000 --- a/src/main/resources/com/projectswg/launcher/core/resources/gui/settings/SettingsForwarderView.fxml +++ /dev/null @@ -1,18 +0,0 @@ - - - - -