Compare commits
171 Commits
Author | SHA1 | Date | |
---|---|---|---|
d75e47a864 | |||
6d063c0d7e | |||
d2c1efe63a | |||
84b1120efb | |||
862616c856 | |||
b2585ff35e | |||
3d85f9ec12 | |||
fb58da96c8 | |||
91c5686fde | |||
9db150efe6 | |||
ad62ab6591 | |||
fabe5afed0 | |||
a67a0b4b4f | |||
9b55a3f08d | |||
c1e729a0fa | |||
d5dfb18417 | |||
602be371d1 | |||
356f80d25c | |||
b6ef0da344 | |||
56c25880e8 | |||
4945381030 | |||
082a4f3b9c | |||
1daaf73554 | |||
4e43d43777 | |||
b2e5ed83a4 | |||
787833f732 | |||
aae97feeca | |||
aede030a7f | |||
3e70e9e4e1 | |||
4629d5f68e | |||
7dd1d4dee2 | |||
f4ed8a4bcb | |||
af84674c26 | |||
4aa1f24461 | |||
9fd5d870cf | |||
e61df73dd3 | |||
c94c8b59af | |||
475717b6b4 | |||
5cc650921b | |||
5b79987dcf | |||
502026bb22 | |||
3746e30ec7 | |||
ad033711f9 | |||
f1f2e95dd2 | |||
9e1c20737d | |||
04d8121ca2 | |||
f4157bae09 | |||
c7eabc37d3 | |||
0bd675fc7c | |||
bcd4e34f7a | |||
7a7d009e29 | |||
e14294fdd6 | |||
98cc37405a | |||
b1b82c423a | |||
32a01547c0 | |||
e19d7cfb3b | |||
d7ddde6c4d | |||
f091cb7a2b | |||
5faa505235 | |||
8aa0555a2a | |||
a7a135c598 | |||
7f168ded43 | |||
cbba1ab70a | |||
ec67a80389 | |||
1759e5c3de | |||
b8f30247ea | |||
7805400e43 | |||
55b9e5986f | |||
7cefa88dcb | |||
8f46e4887f | |||
1be0d68a56 | |||
e9f8af5617 | |||
9d287c6521 | |||
7c4461a275 | |||
d8bd25b438 | |||
218e12714d | |||
3d73879bed | |||
d1c6b746f0 | |||
71e784fdff | |||
1db7bcde18 | |||
8f056468a7 | |||
25874c40f7 | |||
3aaf7e3652 | |||
17e89ba829 | |||
a89f51aa5d | |||
441a9b26b2 | |||
50e9db1fcc | |||
6151f0e71e | |||
8d45fdff84 | |||
2cb852c5cb | |||
fc256a1376 | |||
1353de777e | |||
3f273bf6f8 | |||
7a80132ab0 | |||
c027885364 | |||
4967410f51 | |||
14a23fdfed | |||
7284193981 | |||
eb9601d6cf | |||
5c9ce78ebf | |||
fe0c23b97b | |||
94dbaadaf1 | |||
cd3c8a1852 | |||
18c4953b36 | |||
0cb3df331a | |||
08bb13f994 | |||
a93f2c8411 | |||
e15ef8c485 | |||
2ca25a7bee | |||
bdd86c7683 | |||
442d462843 | |||
37872e6c79 | |||
dff05af62f | |||
05a18765c9 | |||
36f462597a | |||
c52f1f3350 | |||
d4a016771f | |||
8af7c214d2 | |||
71faae3b9a | |||
379f02c41c | |||
be8252ce58 | |||
fb56c9e922 | |||
18810b255b | |||
3370495207 | |||
b0326536c7 | |||
eb6c2538b5 | |||
3563d7449b | |||
7dee85292c | |||
d2b041ef59 | |||
8eee28f353 | |||
e81c5765df | |||
5dd3c4b0a2 | |||
45fe59df83 | |||
3de1bd0218 | |||
22c27bb9ec | |||
2c30e2e76a | |||
0b65231175 | |||
11a40975d9 | |||
182d65a442 | |||
2b1a8b86b2 | |||
ee72dc9a9c | |||
705f4182aa | |||
04b59ebac6 | |||
958fb90822 | |||
9bf6446082 | |||
ab5304dcd9 | |||
fcf729e65a | |||
94c9e46f8b | |||
109402731d | |||
915c8cea6c | |||
19a5aa3d9f | |||
3eace3f6b8 | |||
7ce6a764ec | |||
841fa6b3d4 | |||
b7ca994fa8 | |||
36063054c0 | |||
9937b7c258 | |||
a31a1abc6c | |||
0ad54e978e | |||
ff7dddbd35 | |||
7e42e6b02f | |||
5c6266634a | |||
390f6bc59b | |||
ef986fab05 | |||
ab81759d4d | |||
b87889f5b4 | |||
62dd7da114 | |||
e3b3e50bd2 | |||
b0a6146d49 | |||
eb3fb83673 | |||
92109d353e |
@ -1,94 +0,0 @@
|
|||||||
variables:
|
|
||||||
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
|
|
||||||
|
|
||||||
default:
|
|
||||||
image: gradle:jdk18
|
|
||||||
before_script:
|
|
||||||
- export GRADLE_USER_HOME=`pwd`/.gradle
|
|
||||||
- export TIMESTAMP=$(date -d "$CI_PIPELINE_CREATED_AT" +%s)
|
|
||||||
|
|
||||||
build_test:
|
|
||||||
stage: build
|
|
||||||
script:
|
|
||||||
- gradle --build-cache :exportMetadata -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- version.json
|
|
||||||
expire_in: 2 days
|
|
||||||
|
|
||||||
build_platform_jars:
|
|
||||||
stage: build
|
|
||||||
script:
|
|
||||||
- gradle --build-cache :launcher-dist:build -Pflavor=fat -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
- gradle --build-cache :launcher-dist:build -Pflavor=windows -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
- gradle --build-cache :launcher-dist:build -Pflavor=linux -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
- gradle --build-cache :launcher-dist:build -Pflavor=macos -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
- for f in launcher-dist/build/libs/Inceptum-*-*-*.jar; do mv "$f" "Inceptum-${f##*-}";done
|
|
||||||
- mv Inceptum-fat.jar Inceptum.jar
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- launcher-dist/build/libs
|
|
||||||
- Inceptum-*.jar
|
|
||||||
- Inceptum.jar
|
|
||||||
expire_in: 2 days
|
|
||||||
|
|
||||||
build_wrapper:
|
|
||||||
stage: build
|
|
||||||
script:
|
|
||||||
- gradle --build-cache :wrapper:build -Pflavor=windows -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
- cp wrapper/build/libs/*.exe wrapper.exe
|
|
||||||
- cp wrapper/build/libs/*-all.jar wrapper.jar
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- wrapper/build/libs
|
|
||||||
- wrapper.jar
|
|
||||||
- wrapper.exe
|
|
||||||
expire_in: 2 days
|
|
||||||
|
|
||||||
publish_debug:
|
|
||||||
stage: deploy
|
|
||||||
script:
|
|
||||||
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
- gradle --build-cache build :launcher-dist:publish -Pflavor=fat -Pdist.platformOnly -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
- gradle --build-cache build :launcher-dist:publish -Pflavor=windows -Pdist.platformOnly -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
- gradle --build-cache build :launcher-dist:publish -Pflavor=linux -Pdist.platformOnly -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
- gradle --build-cache build :launcher-dist:publish -Pflavor=macos -Pdist.platformOnly -Ppublic -Ptimestamp=$TIMESTAMP
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
||||||
publish_release:
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_TAG && '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^master/'
|
|
||||||
stage: deploy
|
|
||||||
script:
|
|
||||||
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Prelease
|
|
||||||
- gradle --build-cache build :launcher-dist:publish -Pflavor=fat -Pdist.platformOnly -Ppublic -Prelease
|
|
||||||
- gradle --build-cache build :launcher-dist:publish -Pflavor=windows -Pdist.platformOnly -Ppublic -Prelease
|
|
||||||
- gradle --build-cache build :launcher-dist:publish -Pflavor=linux -Pdist.platformOnly -Ppublic -Prelease
|
|
||||||
- gradle --build-cache build :launcher-dist:publish -Pflavor=macos -Pdist.platformOnly -Ppublic -Prelease
|
|
||||||
|
|
||||||
portable:
|
|
||||||
stage: deploy
|
|
||||||
image: archlinux:latest
|
|
||||||
script:
|
|
||||||
- pacman -Sy p7zip curl jq --noconfirm
|
|
||||||
- mkdir -p portable/jvm
|
|
||||||
- cp wrapper.jar portable/
|
|
||||||
- curl -L "https://github.com/pal1000/mesa-dist-win/releases/download/21.2.5/mesa3d-21.2.5-release-msvc.7z" --output mesa.7z
|
|
||||||
- 7z e mesa.7z -oportable/run/natives/forceload x64/dxil.dll x64/libglapi.dll x64/opengl32.dll
|
|
||||||
- curl -L "https://api.adoptium.net/v3/binary/latest/18/ga/windows/x64/jre/hotspot/normal/eclipse?project=jdk" --output jvm.zip
|
|
||||||
- 7z x jvm.zip -oportable/
|
|
||||||
- mv portable/jdk*/* portable/jvm/
|
|
||||||
- rm -r portable/jdk*
|
|
||||||
- cp packaging/windows/launch.bat portable/
|
|
||||||
- cd portable
|
|
||||||
- 7z a ../portable.7z * -mx9
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- portable.7z
|
|
||||||
expire_in: 2 days
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
||||||
include:
|
|
||||||
- remote: 'https://jfmods.gitlab.io/scripts/docs.yml'
|
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,3 +1,3 @@
|
|||||||
[submodule "packaging/arch-linux"]
|
[submodule "packaging/arch-linux"]
|
||||||
path = packaging/arch-linux
|
path = packaging/arch-linux
|
||||||
url = ssh://aur@aur.archlinux.org/inceptum-git.git
|
url = https://aur.archlinux.org/inceptum-git.git
|
||||||
|
84
.woodpecker.yml
Normal file
84
.woodpecker.yml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#link https://pages.frohnmeyer-wds.de/scripts/docs.yml
|
||||||
|
#include https://pages.frohnmeyer-wds.de/scripts/clone.yml
|
||||||
|
|
||||||
|
steps:
|
||||||
|
export_metadata:
|
||||||
|
image: gradle:jdk22-jammy
|
||||||
|
pull: true
|
||||||
|
commands:
|
||||||
|
- mkdir public
|
||||||
|
- gradle --build-cache :exportMetadata -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
||||||
|
- mv version.json public/
|
||||||
|
build_platform_jars:
|
||||||
|
image: git.frohnmeyer-wds.de/johannes/ci-wine
|
||||||
|
pull: true
|
||||||
|
commands:
|
||||||
|
- ./platform_jars.sh
|
||||||
|
build_wrapper:
|
||||||
|
image: gradle:jdk22-jammy
|
||||||
|
commands:
|
||||||
|
- gradle --build-cache :wrapper:build -Pflavor=windows -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
||||||
|
- cp wrapper/build/libs/*.exe public/wrapper.exe
|
||||||
|
- cp wrapper/build/libs/*-all.jar public/wrapper.jar
|
||||||
|
publish_debug:
|
||||||
|
image: gradle:jdk22-jammy
|
||||||
|
commands:
|
||||||
|
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Ptimestamp=${CI_PIPELINE_STARTED}
|
||||||
|
secrets: [ maven_token, maven_name ]
|
||||||
|
when:
|
||||||
|
- branch: master
|
||||||
|
publish_release:
|
||||||
|
image: gradle:jdk22-jammy
|
||||||
|
commands:
|
||||||
|
- gradle --build-cache build publish -Pflavor=maven -Ppublic -Prelease
|
||||||
|
secrets: [ maven_token, maven_name ]
|
||||||
|
when:
|
||||||
|
- event: tag
|
||||||
|
branch: master
|
||||||
|
portable:
|
||||||
|
image: git.frohnmeyer-wds.de/johannes/ci-wine
|
||||||
|
commands:
|
||||||
|
- mkdir -p portable/jvm
|
||||||
|
- cp public/wrapper.jar portable/
|
||||||
|
- curl -L "https://github.com/pal1000/mesa-dist-win/releases/download/21.2.5/mesa3d-21.2.5-release-msvc.7z" --output mesa.7z
|
||||||
|
- 7z e mesa.7z -oportable/run/natives/forceload x64/dxil.dll x64/libglapi.dll x64/opengl32.dll
|
||||||
|
- curl -L "https://api.adoptium.net/v3/binary/latest/19/ga/windows/x64/jre/hotspot/normal/eclipse?project=jdk" --output jvm.zip
|
||||||
|
- 7z x jvm.zip -oportable/
|
||||||
|
- mv portable/jdk*/* portable/jvm/
|
||||||
|
- rm -r portable/jdk*
|
||||||
|
- cp packaging/windows/launch.bat portable/
|
||||||
|
- cd portable
|
||||||
|
- 7z a ../public/portable.7z * -mx9
|
||||||
|
when:
|
||||||
|
- branch: master
|
||||||
|
publish:
|
||||||
|
image: woodpeckerci/plugin-s3
|
||||||
|
settings:
|
||||||
|
bucket: pages
|
||||||
|
region: nebula
|
||||||
|
path_style: true
|
||||||
|
endpoint: https://s3.frohnmeyer-wds.de
|
||||||
|
access_key: pages
|
||||||
|
secret_key:
|
||||||
|
from_secret: pages_secret
|
||||||
|
source: public/**/*
|
||||||
|
target: /${CI_REPO}/artifacts
|
||||||
|
strip_prefix: public/
|
||||||
|
when:
|
||||||
|
- branch: master
|
||||||
|
publishRelease:
|
||||||
|
image: woodpeckerci/plugin-s3
|
||||||
|
settings:
|
||||||
|
bucket: pages
|
||||||
|
region: nebula
|
||||||
|
path_style: true
|
||||||
|
endpoint: https://s3.frohnmeyer-wds.de
|
||||||
|
access_key: pages
|
||||||
|
secret_key:
|
||||||
|
from_secret: pages_secret
|
||||||
|
source: public/**/*
|
||||||
|
target: /${CI_REPO}/stable
|
||||||
|
strip_prefix: public/
|
||||||
|
when:
|
||||||
|
- event: tag
|
||||||
|
branch: master
|
2
LICENSE
2
LICENSE
@ -1,5 +1,5 @@
|
|||||||
Inceptum - A FOSS Launcher for Minecraft written in Java
|
Inceptum - A FOSS Launcher for Minecraft written in Java
|
||||||
Copyright (C) 2021 JFronny
|
Copyright (C) 2021-2023 JFronny
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
An advanced FOSS Launcher for Minecraft written in Java
|
An advanced FOSS Launcher for Minecraft written in Java
|
||||||
|
|
||||||
For documentation on how to use or install Inceptum, please head to the [wiki](https://jfmods.gitlab.io/inceptum)
|
For documentation on how to use or install Inceptum, please head to the [wiki](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/)
|
||||||
|
|
||||||
## Licenses
|
## Licenses
|
||||||
|
|
||||||
@ -14,7 +14,9 @@ Inceptum utilizes code/libraries/assets from:
|
|||||||
- [imgui-java](https://github.com/SpaiR/imgui-java): The library used for UI
|
- [imgui-java](https://github.com/SpaiR/imgui-java): The library used for UI
|
||||||
- [Dear ImGui](https://github.com/ocornut/imgui): Included and wrapped in imgui-java, UI library
|
- [Dear ImGui](https://github.com/ocornut/imgui): Included and wrapped in imgui-java, UI library
|
||||||
- [LWJGL](https://github.com/LWJGL/lwjgl3): Used as a backend for imgui-java
|
- [LWJGL](https://github.com/LWJGL/lwjgl3): Used as a backend for imgui-java
|
||||||
|
- [java-gi](https://github.com/jwharm/java-gi): The library used for the new UI
|
||||||
|
- [GTK4](https://www.gtk.org/) (and dependencies): Wrapped in java-gi-generated code, the core UI library
|
||||||
- [gson](https://github.com/google/gson): Used for interacting with various APIs and configs
|
- [gson](https://github.com/google/gson): Used for interacting with various APIs and configs
|
||||||
- [Ubuntu](https://design.ubuntu.com/font/): Used with nerd font symbols as the font
|
- [Ubuntu](https://design.ubuntu.com/font/): Used with nerd font symbols as the font
|
||||||
- [meteor-client](https://github.com/MeteorDevelopment/meteor-client): A simple HTTP client
|
- [meteor-client](https://github.com/MeteorDevelopment/meteor-client): A simple HTTP client
|
||||||
- Several of [my other projects](https://gitlab.com/jfmods)
|
- Several of [my other projects](https://git.frohnmeyer-wds.de/explore/repos)
|
||||||
|
@ -10,7 +10,7 @@ description = "Inceptum Docs"
|
|||||||
build-dir = "public"
|
build-dir = "public"
|
||||||
|
|
||||||
[output.html]
|
[output.html]
|
||||||
git-repository-url = "https://gitlab.com/jfmods/Inceptum"
|
git-repository-url = "https://git.frohnmeyer-wds.de/JfMods/Inceptum"
|
||||||
git-repository-icon = "fa-gitlab"
|
git-repository-icon = "fa-git-alt"
|
||||||
edit-url-template = "https://gitlab.com/jfmods/Inceptum/edit/master/{path}"
|
edit-url-template = "https://git.frohnmeyer-wds.de/JfMods/Inceptum/_edit/master/{path}"
|
||||||
site-url = "https://jfmods.gitlab.io/inceptum"
|
site-url = "https://pages.frohnmeyer-wds.de/JfMods/Inceptum/"
|
||||||
|
@ -1,51 +1,25 @@
|
|||||||
import org.ajoberstar.grgit.Grgit
|
import io.gitlab.jfronny.scripts.*
|
||||||
import org.gradle.internal.os.OperatingSystem
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("org.ajoberstar.grgit") version "5.0.0" apply false
|
id("jf.autoversion")
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentVer = "0.0.0+nogit"
|
|
||||||
if (File(".git").exists()) {
|
|
||||||
val grgit: Grgit = Grgit.open(mapOf("dir" to rootProject.projectDir.toString()))
|
|
||||||
currentVer = "0.0.0+notag"
|
|
||||||
val tagList = grgit.tag.list()
|
|
||||||
tagList.sortWith { left, right -> right.commit.dateTime.compareTo(left.commit.dateTime) }
|
|
||||||
if (tagList.isNotEmpty()) {
|
|
||||||
currentVer = tagList[0].name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val timestamp: Long = if (project.hasProperty("timestamp")) "${project.property("timestamp")}".toLong()
|
|
||||||
else (System.currentTimeMillis() / 1000L)
|
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
version = currentVer + if (project.hasProperty("release")) "" else "-$timestamp"
|
version = rootProject.version
|
||||||
group = "io.gitlab.jfronny.inceptum"
|
group = "io.gitlab.jfronny.inceptum"
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Using Inceptum Build Script $version")
|
val flavorProp: String by extra(prop("flavor", "custom"))
|
||||||
|
if (!setOf("custom", "maven", "fat", "windows", "linux", "macos").contains(flavorProp)) throw IllegalStateException("Unsupported flavor: $flavorProp")
|
||||||
val lwjglVersion by extra("3.3.1")
|
val flavor: String by extra(if (flavorProp != "custom") flavorProp else OS.TYPE.codename)
|
||||||
val imguiVersion by extra("1.86.4")
|
|
||||||
val jfCommonsVersion by extra("2022.9.18+16-50-22")
|
|
||||||
val jlhttpVersion by extra("2.6")
|
|
||||||
val flavorProp: String by extra(if (project.hasProperty("flavor")) "${project.property("flavor")}" else "custom")
|
|
||||||
if (flavorProp != "custom" && flavorProp != "maven" && flavorProp != "fat" && flavorProp != "windows" && flavorProp != "linux" && flavorProp != "macos")
|
|
||||||
throw IllegalStateException("Unsupported flavor: $flavorProp")
|
|
||||||
val flavor: String by extra(
|
|
||||||
if (flavorProp != "custom") flavorProp else when (OperatingSystem.current()) {
|
|
||||||
OperatingSystem.WINDOWS -> "windows"
|
|
||||||
OperatingSystem.LINUX -> "linux"
|
|
||||||
OperatingSystem.MAC_OS -> "macos"
|
|
||||||
else -> throw IllegalStateException("Unsupported OS: ${OperatingSystem.current()}")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
val isPublic by extra(project.hasProperty("public"))
|
val isPublic by extra(project.hasProperty("public"))
|
||||||
val isRelease by extra(project.hasProperty("release"))
|
val isRelease by extra(project.hasProperty("release"))
|
||||||
|
|
||||||
|
val buildTime by extra(System.currentTimeMillis())
|
||||||
val wrapperVersion by extra(1)
|
val wrapperVersion by extra(1)
|
||||||
|
|
||||||
|
val lwjglVersion = libs.versions.lwjgl.get()
|
||||||
|
val imguiVersion = libs.versions.imgui.get()
|
||||||
tasks.register("exportMetadata") {
|
tasks.register("exportMetadata") {
|
||||||
doLast {
|
doLast {
|
||||||
projectDir.resolve("version.json").writeText(
|
projectDir.resolve("version.json").writeText(
|
||||||
@ -53,12 +27,13 @@ tasks.register("exportMetadata") {
|
|||||||
{
|
{
|
||||||
"wrapperVersion": $wrapperVersion,
|
"wrapperVersion": $wrapperVersion,
|
||||||
"version": "$version",
|
"version": "$version",
|
||||||
|
"buildTime": $buildTime,
|
||||||
"isPublic": $isPublic,
|
"isPublic": $isPublic,
|
||||||
"isRelease": $isRelease,
|
"isRelease": $isRelease,
|
||||||
"jvm": ${project(":common").extra["javaVersion"]},
|
"jvm": ${project(":common").extra["javaVersion"]},
|
||||||
"repositories": [
|
"repositories": [
|
||||||
"https://repo.maven.apache.org/maven2/",
|
"https://repo.maven.apache.org/maven2/",
|
||||||
"https://gitlab.com/api/v4/projects/35745143/packages/maven"
|
"https://maven.frohnmeyer-wds.de/artifacts/"
|
||||||
],
|
],
|
||||||
"natives": {
|
"natives": {
|
||||||
"windows": [
|
"windows": [
|
||||||
|
@ -4,9 +4,12 @@ plugins {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
|
maven("https://maven.frohnmeyer-wds.de/artifacts")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2")
|
implementation(libs.plugin.shadow)
|
||||||
implementation("de.undercouch:gradle-download-task:5.1.2")
|
implementation(libs.plugin.download)
|
||||||
|
implementation(libs.plugin.jf.convention)
|
||||||
|
implementation(libs.plugin.jlink)
|
||||||
}
|
}
|
2
buildSrc/java-offline
Executable file
2
buildSrc/java-offline
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
firejail --net=none "$G_ORIGINAL_EXECUTABLE" "$@"
|
@ -1 +1,8 @@
|
|||||||
rootProject.name="inceptum-conventions"
|
rootProject.name="inceptum-conventions"
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
versionCatalogs {
|
||||||
|
create("libs") {
|
||||||
|
from(files("../gradle/libs.versions.toml"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import java.io.FileOutputStream
|
|
||||||
import de.undercouch.gradle.tasks.download.Download
|
import de.undercouch.gradle.tasks.download.Download
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
application
|
application
|
||||||
id("inceptum.java-conventions")
|
id("inceptum.java")
|
||||||
id("com.github.johnrengelman.shadow")
|
com.github.johnrengelman.shadow
|
||||||
id("de.undercouch.download")
|
de.undercouch.download
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class FileOutput : DefaultTask() {
|
abstract class FileOutput : DefaultTask() {
|
||||||
@ -18,14 +18,14 @@ val bootstrapArch = "i686"
|
|||||||
|
|
||||||
val downloadBootstrap by tasks.registering(Download::class) {
|
val downloadBootstrap by tasks.registering(Download::class) {
|
||||||
src("https://maven.fabricmc.net/net/fabricmc/fabric-installer-native-bootstrap/windows-${bootstrapArch}/${bootstrapVersion}/windows-${bootstrapArch}-${bootstrapVersion}.exe")
|
src("https://maven.fabricmc.net/net/fabricmc/fabric-installer-native-bootstrap/windows-${bootstrapArch}/${bootstrapVersion}/windows-${bootstrapArch}-${bootstrapVersion}.exe")
|
||||||
dest(project.buildDir)
|
dest(project.layout.buildDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
val nativeExe by tasks.registering(FileOutput::class) {
|
val nativeExe by tasks.registering(FileOutput::class) {
|
||||||
dependsOn(downloadBootstrap)
|
dependsOn(downloadBootstrap)
|
||||||
dependsOn(tasks.shadowJar)
|
dependsOn(tasks.shadowJar)
|
||||||
|
|
||||||
output = file("$buildDir/libs/${project.name}-${project.version}.exe")
|
output = project.layout.buildDirectory.file("libs/${project.name}-${project.version}.exe").get().asFile
|
||||||
outputs.upToDateWhen { false }
|
outputs.upToDateWhen { false }
|
||||||
|
|
||||||
doFirst {
|
doFirst {
|
||||||
@ -44,3 +44,7 @@ val nativeExe by tasks.registering(FileOutput::class) {
|
|||||||
if (rootProject.extra["flavor"] == "windows") {
|
if (rootProject.extra["flavor"] == "windows") {
|
||||||
tasks.build.get().dependsOn(nativeExe)
|
tasks.build.get().dependsOn(nativeExe)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.runShadow {
|
||||||
|
workingDir = rootProject.projectDir
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
application
|
application
|
||||||
id("inceptum.java-conventions")
|
id("inceptum.java")
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
@ -10,3 +10,5 @@ publishing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.run.get().workingDir = rootProject.projectDir
|
13
buildSrc/src/main/kotlin/inceptum.gson-compile.gradle.kts
Normal file
13
buildSrc/src/main/kotlin/inceptum.gson-compile.gradle.kts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
plugins {
|
||||||
|
id("inceptum.library")
|
||||||
|
}
|
||||||
|
|
||||||
|
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||||
|
dependencies {
|
||||||
|
compileOnly(libs.findLibrary("commons-serialize-generator-annotations").orElseThrow())
|
||||||
|
annotationProcessor(libs.findLibrary("commons-serialize-generator").orElseThrow())
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile> {
|
||||||
|
options.compilerArgs.addAll(listOf("-AserializeProcessorNoReflect"))
|
||||||
|
}
|
@ -1,35 +0,0 @@
|
|||||||
plugins {
|
|
||||||
`java-library`
|
|
||||||
`maven-publish`
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
setUrl("https://gitlab.com/api/v4/projects/35745143/packages/maven")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly("org.jetbrains:annotations:23.0.0")
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
repositories {
|
|
||||||
if (rootProject.extra["isPublic"] == true) {
|
|
||||||
maven {
|
|
||||||
url = uri("https://gitlab.com/api/v4/projects/30862253/packages/maven")
|
|
||||||
name = "gitlab"
|
|
||||||
|
|
||||||
credentials(HttpHeaderCredentials::class) {
|
|
||||||
name = "Job-Token"
|
|
||||||
value = System.getenv()["CI_JOB_TOKEN"]
|
|
||||||
}
|
|
||||||
authentication {
|
|
||||||
create<HttpHeaderAuthentication>("header")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mavenLocal()
|
|
||||||
}
|
|
||||||
}
|
|
52
buildSrc/src/main/kotlin/inceptum.java.gradle.kts
Normal file
52
buildSrc/src/main/kotlin/inceptum.java.gradle.kts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
plugins {
|
||||||
|
jf.java
|
||||||
|
`maven-publish`
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven("https://maven.frohnmeyer-wds.de/artifacts")
|
||||||
|
}
|
||||||
|
|
||||||
|
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||||
|
dependencies {
|
||||||
|
compileOnly(libs.findLibrary("annotations").orElseThrow())
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
|
||||||
|
if (rootProject.extra["isPublic"] == true) {
|
||||||
|
maven("https://maven.frohnmeyer-wds.de/artifacts") {
|
||||||
|
name = "public"
|
||||||
|
|
||||||
|
credentials(PasswordCredentials::class) {
|
||||||
|
username = System.getenv()["MAVEN_NAME"]
|
||||||
|
password = System.getenv()["MAVEN_TOKEN"]
|
||||||
|
}
|
||||||
|
authentication {
|
||||||
|
create<BasicAuthentication>("basic")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
if (hasProperty("offline")) {
|
||||||
|
tasks.withType(JavaExec::class) {
|
||||||
|
environment("G_ORIGINAL_EXECUTABLE", executable ?: "java")
|
||||||
|
val originalMetadata = javaLauncher.get().metadata
|
||||||
|
val field = org.gradle.api.internal.provider.AbstractProperty::class.java.getDeclaredField("value")
|
||||||
|
field.isAccessible = true
|
||||||
|
val customLauncher = object: JavaLauncher {
|
||||||
|
override fun getMetadata(): JavaInstallationMetadata = originalMetadata
|
||||||
|
override fun getExecutablePath(): RegularFile = rootProject.layout.projectDirectory
|
||||||
|
.dir("buildSrc")
|
||||||
|
.file("java-offline")
|
||||||
|
}
|
||||||
|
field.set(javaLauncher, org.gradle.api.internal.provider.Providers.of(customLauncher))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("inceptum.java-conventions")
|
id("inceptum.java")
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
1
common/.gitignore
vendored
1
common/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
src/main/java/io/gitlab/jfronny/inceptum/common/BuildMetadata.java
|
|
@ -1,25 +1,32 @@
|
|||||||
|
import io.gitlab.jfronny.scripts.*
|
||||||
|
import javax.lang.model.element.Modifier
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("inceptum.library-conventions")
|
inceptum.library
|
||||||
|
jf.codegen
|
||||||
|
inceptum.`gson-compile`
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api("io.gitlab.jfronny:commons:${rootProject.extra["jfCommonsVersion"]}")
|
api(libs.bundles.commons)
|
||||||
api("io.gitlab.jfronny:commons-gson:${rootProject.extra["jfCommonsVersion"]}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val javaVersion by extra(project.java.targetCompatibility)
|
val javaVersion by extra(project.java.targetCompatibility)
|
||||||
|
|
||||||
projectDir.resolve("src/main/java/io/gitlab/jfronny/inceptum/common/BuildMetadata.java").writeText(
|
sourceSets {
|
||||||
"""
|
main {
|
||||||
package io.gitlab.jfronny.inceptum.common;
|
generate(project) {
|
||||||
|
`class`("io.gitlab.jfronny.inceptum.common", "BuildMetadata") {
|
||||||
|
modifiers(Modifier.PUBLIC)
|
||||||
|
val modifiers = arrayOf(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.ComparableVersion;
|
field("VERSION", versionS, *modifiers)
|
||||||
|
field("BUILD_TIME", rootProject.extra["buildTime"] as Long, *modifiers)
|
||||||
public class BuildMetadata {
|
field("IS_PUBLIC", rootProject.extra["isPublic"] as Boolean, *modifiers)
|
||||||
public static final ComparableVersion VERSION = new ComparableVersion("$version");
|
field("IS_RELEASE", rootProject.extra["isRelease"] as Boolean, *modifiers)
|
||||||
public static final boolean IS_PUBLIC = ${rootProject.extra["isPublic"]};
|
field("VM_VERSION", javaVersion.majorVersion.toInt(), *modifiers)
|
||||||
public static final boolean IS_RELEASE = ${rootProject.extra["isRelease"]};
|
field("WRAPPER_VERSION", rootProject.extra["wrapperVersion"] as Int, *modifiers)
|
||||||
public static final int VM_VERSION = $javaVersion;
|
|
||||||
public static final int WRAPPER_VERSION = ${rootProject.extra["wrapperVersion"]};
|
|
||||||
}
|
}
|
||||||
""".trimIndent())
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.serialize.SerializeReader;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.Token;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonWriter;
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingBiConsumer;
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GList {
|
||||||
|
public static <T, TEx extends Exception, Reader extends SerializeReader<TEx, ?>> List<T> read(Reader reader, ThrowingFunction<Reader, T, TEx> read) throws TEx {
|
||||||
|
if (reader.isLenient() && reader.peek() != Token.BEGIN_ARRAY) return List.of(read.apply(reader));
|
||||||
|
reader.beginArray();
|
||||||
|
List<T> res = new LinkedList<>();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
if (reader.peek() == Token.NULL) {
|
||||||
|
reader.nextNull();
|
||||||
|
res.add(null);
|
||||||
|
} else res.add(read.apply(reader));
|
||||||
|
}
|
||||||
|
reader.endArray();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void write(JsonWriter writer, List<T> list, ThrowingBiConsumer<T, JsonWriter, IOException> write) throws IOException {
|
||||||
|
writer.beginArray();
|
||||||
|
for (T t : list) write.accept(t, writer);
|
||||||
|
writer.endArray();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.serialize.SerializeReader;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.SerializeWriter;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonTransport;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.json.JsonWriter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
|
public class GsonPreset {
|
||||||
|
public static Config CONFIG = new Config();
|
||||||
|
public static Api API = new Api();
|
||||||
|
|
||||||
|
public static class Config extends JsonTransport {
|
||||||
|
public static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> void configure(Reader reader) {
|
||||||
|
reader.setSerializeSpecialFloatingPointValues(true);
|
||||||
|
reader.setLenient(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void configure(Writer writer) {
|
||||||
|
writer.setSerializeNulls(true);
|
||||||
|
writer.setSerializeSpecialFloatingPointValues(true);
|
||||||
|
writer.setLenient(true);
|
||||||
|
if (writer instanceof JsonWriter jw) {
|
||||||
|
jw.setIndent(" ");
|
||||||
|
jw.setOmitQuotes(true);
|
||||||
|
jw.setNewline("\n");
|
||||||
|
jw.setCommentUnexpectedNames(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonWriter createWriter(Writer target) throws IOException {
|
||||||
|
JsonWriter jw = super.createWriter(target);
|
||||||
|
configure(jw);
|
||||||
|
return jw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonReader createReader(Reader source) {
|
||||||
|
JsonReader jr = super.createReader(source);
|
||||||
|
configure(jr);
|
||||||
|
return jr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Api extends JsonTransport {
|
||||||
|
public static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> void configure(Reader reader) {
|
||||||
|
reader.setSerializeSpecialFloatingPointValues(true);
|
||||||
|
reader.setLenient(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void configure(Writer writer) {
|
||||||
|
writer.setSerializeNulls(false);
|
||||||
|
writer.setSerializeSpecialFloatingPointValues(true);
|
||||||
|
writer.setLenient(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonWriter createWriter(Writer target) throws IOException {
|
||||||
|
JsonWriter jw = super.createWriter(target);
|
||||||
|
configure(jw);
|
||||||
|
return jw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonReader createReader(Reader source) {
|
||||||
|
JsonReader jr = super.createReader(source);
|
||||||
|
configure(jr);
|
||||||
|
return jr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,30 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common;
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
import io.gitlab.jfronny.commons.serialize.generator.annotations.GComment;
|
||||||
import io.gitlab.jfronny.gson.stream.*;
|
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
|
||||||
import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateChannel;
|
import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateChannel;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@GSerializable(isStatic = true)
|
||||||
public class InceptumConfig {
|
public class InceptumConfig {
|
||||||
|
@GComment("Whether to show snapshots in the version selector for new instances")
|
||||||
public static boolean snapshots = false;
|
public static boolean snapshots = false;
|
||||||
|
@GComment("Whether to launch the ImGUI in dark mode\nConfigurable in Settings->Dark Theme")
|
||||||
public static boolean darkTheme = false;
|
public static boolean darkTheme = false;
|
||||||
|
@GComment("Whether the GTK UI should default to a list view instead of a grid")
|
||||||
|
public static boolean listView = false;
|
||||||
|
@GComment("Whether to require an account to launch the game\nIntended to allow running the game from USB sticks on constrained networks")
|
||||||
public static boolean enforceAccount = true;
|
public static boolean enforceAccount = true;
|
||||||
|
@GComment("The currently selected account\nUsed to launch the game")
|
||||||
public static String lastAccount = null;
|
public static String lastAccount = null;
|
||||||
|
@GComment("The last name used for an offline session")
|
||||||
public static String offlineAccountLastName = null;
|
public static String offlineAccountLastName = null;
|
||||||
|
@GComment("The update channel. Either \"CI\" or \"Stable\"\nI personally recommend the CI channel as it gets the latest fixes and features quicker")
|
||||||
public static UpdateChannel channel = UpdateChannel.Stable;
|
public static UpdateChannel channel = UpdateChannel.Stable;
|
||||||
|
@GComment("The author name to add to packs where the metadata format requires specifying one")
|
||||||
public static String authorName = "Inceptum";
|
public static String authorName = "Inceptum";
|
||||||
|
|
||||||
public static void load() throws IOException {
|
public static void load() throws IOException {
|
||||||
@ -25,81 +35,20 @@ public class InceptumConfig {
|
|||||||
Path json = MetaHolder.BASE_PATH.resolve("inceptum.json");
|
Path json = MetaHolder.BASE_PATH.resolve("inceptum.json");
|
||||||
if (Files.exists(gLaunch2)) {
|
if (Files.exists(gLaunch2)) {
|
||||||
Files.move(gLaunch2, MetaHolder.CONFIG_PATH);
|
Files.move(gLaunch2, MetaHolder.CONFIG_PATH);
|
||||||
} if (Files.exists(json)) {
|
} else if (Files.exists(json)) {
|
||||||
Files.move(json, MetaHolder.CONFIG_PATH);
|
Files.move(json, MetaHolder.CONFIG_PATH);
|
||||||
} else {
|
} else {
|
||||||
saveConfig();
|
saveConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try (Reader reader = Files.newBufferedReader(MetaHolder.CONFIG_PATH);
|
GC_InceptumConfig.deserialize(MetaHolder.CONFIG_PATH, GsonPreset.CONFIG);
|
||||||
JsonReader jr = GsonHolder.getGson().newJsonReader(reader)) {
|
|
||||||
jr.beginObject();
|
|
||||||
while (jr.peek() != JsonToken.END_OBJECT) {
|
|
||||||
String name = null;
|
|
||||||
try {
|
|
||||||
name = jr.nextName();
|
|
||||||
switch (name) {
|
|
||||||
case "snapshots" -> snapshots = jr.nextBoolean();
|
|
||||||
case "darkTheme" -> darkTheme = jr.nextBoolean();
|
|
||||||
case "enforceAccount" -> enforceAccount = jr.nextBoolean();
|
|
||||||
case "lastAccount" -> lastAccount = nullableString(jr);
|
|
||||||
case "offlineAccountLastName" -> offlineAccountLastName = nullableString(jr);
|
|
||||||
case "channel" -> {
|
|
||||||
try {
|
|
||||||
channel = UpdateChannel.valueOf(jr.nextString());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Utils.LOGGER.error("Could not read channel", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "authorName" -> authorName = jr.nextString();
|
|
||||||
default -> {
|
|
||||||
Utils.LOGGER.error("Unexpected entry name: " + name);
|
|
||||||
jr.skipValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
|
||||||
if (name == null) Utils.LOGGER.error("Could not read config entry", t);
|
|
||||||
else Utils.LOGGER.error("Could not read config entry: " + name, t);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jr.endObject();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void saveConfig() {
|
public static void saveConfig() {
|
||||||
try (Writer writer = Files.newBufferedWriter(MetaHolder.CONFIG_PATH);
|
try {
|
||||||
JsonWriter jw = GsonHolder.getGson().newJsonWriter(writer)) {
|
GC_InceptumConfig.serialize(MetaHolder.CONFIG_PATH, GsonPreset.CONFIG);
|
||||||
jw.beginObject()
|
|
||||||
.comment("Whether to show snapshots in the version selector for new instances")
|
|
||||||
.name("snapshots").value(snapshots)
|
|
||||||
.comment("Whether to launch the GUI in dark mode")
|
|
||||||
.comment("Configurable in Settings->Dark Theme")
|
|
||||||
.name("darkTheme").value(darkTheme)
|
|
||||||
.comment("Whether to require an account to launch the game")
|
|
||||||
.comment("Intended to allow running the game from USB sticks on constrained networks")
|
|
||||||
.name("enforceAccount").value(enforceAccount)
|
|
||||||
.comment("The currently selected account")
|
|
||||||
.comment("Used to launch the game")
|
|
||||||
.name("lastAccount").value(lastAccount)
|
|
||||||
.comment("The last name used for an offline session")
|
|
||||||
.name("offlineAccountLastName").value(offlineAccountLastName)
|
|
||||||
.comment("The update channel. Either \"CI\" or \"Stable\"")
|
|
||||||
.comment("I personally recommend the CI channel as it gets the latest fixes and features quicker")
|
|
||||||
.name("channel").value(channel.toString())
|
|
||||||
.comment("The author name to add to packs where the metadata format requires specifying one")
|
|
||||||
.name("authorName").value(authorName)
|
|
||||||
.endObject();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Utils.LOGGER.error("Could not save config", e);
|
Utils.LOGGER.error("Could not save config", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String nullableString(JsonReader jr) throws IOException {
|
|
||||||
if (jr.peek() == JsonToken.NULL) {
|
|
||||||
jr.nextNull();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return jr.nextString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common;
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.HttpUtils;
|
import io.gitlab.jfronny.commons.http.client.HttpClient;
|
||||||
import io.gitlab.jfronny.commons.log.Logger;
|
import io.gitlab.jfronny.commons.logger.HotswapLoggerFinder;
|
||||||
import io.gitlab.jfronny.commons.log.StdoutLogger;
|
import io.gitlab.jfronny.commons.logger.StdoutLogger;
|
||||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class InceptumEnvironmentInitializer {
|
public class InceptumEnvironmentInitializer {
|
||||||
public static void initialize() throws IOException {
|
public static void initialize() throws IOException {
|
||||||
Logger.registerFactory(InceptumEnvironmentInitializer::defaultFactory);
|
HttpClient.scheduleOnlineCheck();
|
||||||
HttpUtils.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
|
HotswapLoggerFinder.updateAllStrategies(InceptumEnvironmentInitializer::defaultFactory);
|
||||||
GsonHolder.register();
|
HttpClient.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
|
||||||
InceptumConfig.load();
|
InceptumConfig.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Logger defaultFactory(String name) {
|
public static SystemLoggerPlus defaultFactory(String name, Module module, System.Logger.Level level) {
|
||||||
return new StdoutLogger(name, true, true, true);
|
return StdoutLogger.fancy(name, module, level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ public class MetaHolder {
|
|||||||
case WINDOWS -> getPath(System.getenv("APPDATA"));
|
case WINDOWS -> getPath(System.getenv("APPDATA"));
|
||||||
case MAC_OS -> getPath(System.getProperty("user.home")).resolve("Library").resolve("Application Support");
|
case MAC_OS -> getPath(System.getProperty("user.home")).resolve("Library").resolve("Application Support");
|
||||||
case LINUX -> {
|
case LINUX -> {
|
||||||
String s = System.getenv().get("XDG_CONFIG_HOME");
|
String s = System.getenv("XDG_CONFIG_HOME");
|
||||||
if (s == null)
|
if (s == null)
|
||||||
yield getPath(System.getProperty("user.home")).resolve(".config");
|
yield getPath(System.getProperty("user.home")).resolve(".config");
|
||||||
else
|
else
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common;
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.HashUtils;
|
import io.gitlab.jfronny.commons.http.client.HttpClient;
|
||||||
import io.gitlab.jfronny.commons.HttpUtils;
|
import io.gitlab.jfronny.commons.io.HashUtils;
|
||||||
import io.gitlab.jfronny.commons.cache.FileBackedOperationResultCache;
|
import io.gitlab.jfronny.commons.serialize.json.JsonReader;
|
||||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
import io.gitlab.jfronny.commons.serialize.json.JsonTransport;
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingBiFunction;
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
|
||||||
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@ -17,10 +18,10 @@ import java.nio.file.Path;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class Net {
|
public class Net {
|
||||||
private static final FileBackedOperationResultCache OBJECT_CACHE = new FileBackedOperationResultCache(MetaHolder.CACHE_DIR);
|
private static final ObjectCache OBJECT_CACHE = new ObjectCache(MetaHolder.CACHE_DIR);
|
||||||
|
|
||||||
public static byte[] downloadData(String url) throws IOException, URISyntaxException {
|
public static byte[] downloadData(String url) throws IOException, URISyntaxException {
|
||||||
try (InputStream is = HttpUtils.get(url).sendInputStream()) {
|
try (InputStream is = HttpClient.get(url).sendInputStream()) {
|
||||||
return is.readAllBytes();
|
return is.readAllBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,43 +33,59 @@ public class Net {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, Class<T> type) throws IOException {
|
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func) throws IOException {
|
||||||
return downloadObject(url, type, true);
|
return downloadObject(url, func, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, Class<T> type, boolean cache) throws IOException {
|
public static <T> T downloadJObject(String url, ThrowingFunction<JsonReader, T, IOException> func) throws IOException {
|
||||||
return downloadObject(url, () -> HttpUtils.get(url).sendString(), type, cache);
|
return downloadJObject(url, func, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, Type type) throws IOException {
|
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
||||||
return downloadObject(url, type, true);
|
return downloadObject(url, () -> HttpClient.get(url).sendString(), func, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, Type type, String apiKey) throws IOException {
|
public static <T> T downloadJObject(String url, ThrowingFunction<JsonReader, T, IOException> func, boolean cache) throws IOException {
|
||||||
return downloadObject(url, () -> HttpUtils.get(url).header("x-api-key", apiKey).sendString(), type, true);
|
return downloadJObject(url, () -> HttpClient.get(url).sendString(), func, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, Type type, boolean cache) throws IOException {
|
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, String apiKey) throws IOException {
|
||||||
return downloadObject(url, () -> HttpUtils.get(url).sendString(), type, cache);
|
return downloadObject(url, () -> downloadStringAuthenticated(url, apiKey), func, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, String sha1, Class<T> type) throws IOException {
|
public static <T> T downloadJObject(String url, ThrowingFunction<JsonReader, T, IOException> func, String apiKey) throws IOException {
|
||||||
return downloadObject(url, sha1, type, true);
|
return downloadJObject(url, () -> downloadStringAuthenticated(url, apiKey), func, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, String sha1, Class<T> type, boolean cache) throws IOException {
|
public static <T> T downloadObject(String url, String sha1, ThrowingFunction<String, T, IOException> func) throws IOException {
|
||||||
return downloadObject(url, () -> downloadString(url, sha1), type, cache);
|
return downloadObject(url, sha1, func, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T downloadObject(String url, ThrowingSupplier<String, Exception> sourceString, Type type, boolean cache) throws IOException {
|
public static <T> T downloadJObject(String url, String sha1, ThrowingFunction<JsonReader, T, IOException> func) throws IOException {
|
||||||
|
return downloadJObject(url, sha1, func, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T downloadObject(String url, String sha1, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
||||||
|
return downloadObject(url, () -> downloadString(url, sha1), func, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T downloadJObject(String url, String sha1, ThrowingFunction<JsonReader, T, IOException> func, boolean cache) throws IOException {
|
||||||
|
return downloadJObject(url, () -> downloadString(url, sha1), func, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T downloadObject(String url, ThrowingSupplier<String, Exception> sourceString, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
||||||
try {
|
try {
|
||||||
ThrowingSupplier<T, Exception> builder = () -> GsonHolder.getGson().fromJson(sourceString.get(), type);
|
ThrowingSupplier<T, Exception> builder = () -> func.apply(sourceString.get());
|
||||||
return cache ? OBJECT_CACHE.get(HashUtils.sha1(url.getBytes(StandardCharsets.UTF_8)), builder, type) : builder.get();
|
return cache ? OBJECT_CACHE.get(HashUtils.sha1(url.getBytes(StandardCharsets.UTF_8)), sourceString, func) : builder.get();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IOException("Could not download object and no cache exists", e);
|
throw new IOException("Could not download object and no cache exists", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T downloadJObject(String url, ThrowingSupplier<String, Exception> sourceString, ThrowingFunction<JsonReader, T, IOException> func, boolean cache) throws IOException {
|
||||||
|
return downloadObject(url, sourceString, s -> func.apply(GsonPreset.API.createReader(s)), cache);
|
||||||
|
}
|
||||||
|
|
||||||
public static String buildUrl(String host, String url, Map<String, String> params) {
|
public static String buildUrl(String host, String url, Map<String, String> params) {
|
||||||
StringBuilder res = new StringBuilder(host);
|
StringBuilder res = new StringBuilder(host);
|
||||||
if (res.toString().endsWith("/")) res = new StringBuilder(res.substring(0, res.length() - 1));
|
if (res.toString().endsWith("/")) res = new StringBuilder(res.substring(0, res.length() - 1));
|
||||||
@ -85,13 +102,17 @@ public class Net {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String downloadString(String url) throws IOException, URISyntaxException {
|
public static String downloadString(String url) throws IOException, URISyntaxException {
|
||||||
return HttpUtils.get(url).sendString();
|
return HttpClient.get(url).sendString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String downloadString(String url, String sha1) throws IOException, URISyntaxException {
|
public static String downloadString(String url, String sha1) throws IOException, URISyntaxException {
|
||||||
return new String(downloadData(url, sha1), StandardCharsets.UTF_8);
|
return new String(downloadData(url, sha1), StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String downloadStringAuthenticated(String url, String apiKey) throws IOException, URISyntaxException {
|
||||||
|
return HttpClient.get(url).header("x-api-key", apiKey).sendString();
|
||||||
|
}
|
||||||
|
|
||||||
public static void downloadFile(String url, Path path) throws IOException, URISyntaxException {
|
public static void downloadFile(String url, Path path) throws IOException, URISyntaxException {
|
||||||
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
|
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
|
||||||
Files.write(path, downloadData(url));
|
Files.write(path, downloadData(url));
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.io.JFiles;
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class ObjectCache {
|
||||||
|
private final ConcurrentHashMap<String, Object> container = new ConcurrentHashMap<>();
|
||||||
|
private final Path cacheDir;
|
||||||
|
|
||||||
|
public ObjectCache(Path cacheDir) {
|
||||||
|
this.cacheDir = cacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(String key) throws IOException {
|
||||||
|
container.remove(key);
|
||||||
|
Files.delete(cacheDir.resolve(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() throws IOException {
|
||||||
|
container.clear();
|
||||||
|
JFiles.clearDirectory(cacheDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T, TEx extends Throwable> T get(String key, ThrowingSupplier<String, ? extends TEx> download, ThrowingFunction<String, T, ? extends TEx> builder) throws IOException, TEx {
|
||||||
|
if (!container.containsKey(key)) {
|
||||||
|
Path cd = cacheDir.resolve(key);
|
||||||
|
if (Files.exists(cd)) container.put(key, builder.apply(Files.readString(cd)));
|
||||||
|
else container.put(key, builder.apply(download.get()));
|
||||||
|
}
|
||||||
|
//noinspection unchecked
|
||||||
|
return (T) container.get(key);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common;
|
|
||||||
|
|
||||||
public class OutdatedException extends RuntimeException {
|
|
||||||
public OutdatedException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class containing methods for nop lambdas. Use these instead of () -> {}
|
|
||||||
*/
|
|
||||||
public class R {
|
|
||||||
public static void nop() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void nop(Object a1) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void nop(Object a1, Object a2) {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +1,15 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common;
|
package io.gitlab.jfronny.inceptum.common;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.ComparableVersion;
|
|
||||||
import io.gitlab.jfronny.commons.OSUtils;
|
import io.gitlab.jfronny.commons.OSUtils;
|
||||||
import io.gitlab.jfronny.commons.io.JFiles;
|
import io.gitlab.jfronny.commons.serialize.json.JsonTransport;
|
||||||
import io.gitlab.jfronny.inceptum.common.api.GitlabApi;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.api.MavenApi;
|
import io.gitlab.jfronny.inceptum.common.api.MavenApi;
|
||||||
import io.gitlab.jfronny.inceptum.common.model.gitlab.*;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.model.inceptum.*;
|
import io.gitlab.jfronny.inceptum.common.model.inceptum.*;
|
||||||
import io.gitlab.jfronny.inceptum.common.model.maven.*;
|
import io.gitlab.jfronny.inceptum.common.model.maven.*;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
import javax.xml.stream.XMLStreamException;
|
import javax.xml.stream.XMLStreamException;
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -23,9 +19,12 @@ import java.util.stream.Collectors;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class Updater {
|
public class Updater {
|
||||||
|
public static final String PROJECT_MAVEN = "https://maven.frohnmeyer-wds.de/artifacts/";
|
||||||
|
private static final String ARTIFACTS_URL = "https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/";
|
||||||
|
private static final String STABLE_URL = "https://pages.frohnmeyer-wds.de/JfMods/Inceptum/stable/";
|
||||||
|
|
||||||
public static UpdateMetadata getUpdate() {
|
public static UpdateMetadata getUpdate(boolean versionCompare, boolean checkEnv) throws UpdateCheckException {
|
||||||
return Updater.check(InceptumConfig.channel, BuildMetadata.VERSION, channel -> {
|
return Updater.check(InceptumConfig.channel, versionCompare, checkEnv, channel -> {
|
||||||
Utils.LOGGER.error("No stable version was found, switching to experimental channel");
|
Utils.LOGGER.error("No stable version was found, switching to experimental channel");
|
||||||
InceptumConfig.channel = channel;
|
InceptumConfig.channel = channel;
|
||||||
InceptumConfig.saveConfig();
|
InceptumConfig.saveConfig();
|
||||||
@ -33,36 +32,29 @@ public class Updater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void update(UpdateMetadata source, boolean relaunch) throws IOException, URISyntaxException {
|
public static void update(UpdateMetadata source, boolean relaunch) throws IOException, URISyntaxException {
|
||||||
if (Runtime.version().feature() < source.jvm) {
|
Utils.LOGGER.info("Downloading version " + source.version());
|
||||||
throw new OutdatedException("A newer JVM version is required for the current build of Inceptum. Please update!");
|
|
||||||
} else if (source.wrapperVersion > BuildMetadata.WRAPPER_VERSION) {
|
|
||||||
throw new OutdatedException("The current build of Inceptum requires a newer wrapper version. Please update!");
|
|
||||||
} else if (source.wrapperVersion < BuildMetadata.WRAPPER_VERSION) {
|
|
||||||
throw new OutdatedException("The current build of Inceptum requires an older wrapper version. Please update!");
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils.LOGGER.info("Downloading version " + source.version);
|
WrapperConfig config = new WrapperConfig(
|
||||||
|
new LinkedHashSet<>(),
|
||||||
|
new LinkedHashSet<>(source.repositories()),
|
||||||
|
new HashMap<>()
|
||||||
|
);
|
||||||
|
source.natives().forEach((k, v) -> config.natives().put(k, new LinkedHashSet<>(v)));
|
||||||
|
|
||||||
WrapperConfig config = new WrapperConfig();
|
DependencyNode node = downloadLibrary(source.repositories(), "io.gitlab.jfronny.inceptum:launcher-dist:" + source.version(), config.libraries());
|
||||||
config.natives = new HashMap<>();
|
|
||||||
config.libraries = new LinkedHashSet<>();
|
|
||||||
config.repositories = new LinkedHashSet<>(source.repositories);
|
|
||||||
source.natives.forEach((k, v) -> config.natives.put(k, new LinkedHashSet<>(v)));
|
|
||||||
|
|
||||||
DependencyNode node = downloadLibrary(source.repositories, "io.gitlab.jfronny.inceptum:launcher-dist:" + source.version, config.libraries);
|
|
||||||
Utils.LOGGER.info("Downloaded Dependencies:\n" + node);
|
Utils.LOGGER.info("Downloaded Dependencies:\n" + node);
|
||||||
|
|
||||||
List<String> currentLibraries = new LinkedList<>(config.libraries);
|
List<String> currentLibraries = new LinkedList<>(config.libraries());
|
||||||
if (source.natives.containsKey(Utils.getCurrentFlavor())) {
|
if (source.natives().containsKey(Utils.getCurrentFlavor())) {
|
||||||
Set<String> natives = new LinkedHashSet<>();
|
Set<String> natives = new LinkedHashSet<>();
|
||||||
for (String lib : source.natives.get(Utils.getCurrentFlavor())){
|
for (String lib : source.natives().get(Utils.getCurrentFlavor())) {
|
||||||
downloadLibrary(source.repositories, lib, natives);
|
downloadLibrary(source.repositories(), lib, natives);
|
||||||
}
|
}
|
||||||
currentLibraries.addAll(natives);
|
currentLibraries.addAll(natives);
|
||||||
config.natives.put(Utils.getCurrentFlavor(), natives);
|
config.natives().put(Utils.getCurrentFlavor(), natives);
|
||||||
}
|
}
|
||||||
|
|
||||||
JFiles.writeObject(MetaHolder.WRAPPER_CONFIG_PATH, config);
|
GC_WrapperConfig.serialize(config, MetaHolder.WRAPPER_CONFIG_PATH, GsonPreset.CONFIG);
|
||||||
|
|
||||||
if (relaunch) {
|
if (relaunch) {
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
@ -71,7 +63,7 @@ public class Updater {
|
|||||||
"-cp",
|
"-cp",
|
||||||
buildClasspath(currentLibraries.stream())
|
buildClasspath(currentLibraries.stream())
|
||||||
.map(Path::toString)
|
.map(Path::toString)
|
||||||
.collect(Collectors.joining("" + File.pathSeparatorChar))
|
.collect(Collectors.joining(String.valueOf(File.pathSeparatorChar)))
|
||||||
).inheritIO().start();
|
).inheritIO().start();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Utils.LOGGER.error("Could not relaunch", e);
|
Utils.LOGGER.error("Could not relaunch", e);
|
||||||
@ -81,107 +73,81 @@ public class Updater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<Path> getLaunchClasspath(WrapperConfig wrapperConfig) throws IOException, URISyntaxException {
|
public static List<Path> getLaunchClasspath(WrapperConfig wrapperConfig) throws IOException, URISyntaxException {
|
||||||
Set<String> natives = wrapperConfig.natives.get(Utils.getCurrentFlavor());
|
Set<String> natives = wrapperConfig.natives().get(Utils.getCurrentFlavor());
|
||||||
if (natives == null) natives = new LinkedHashSet<>();
|
if (natives == null) natives = new LinkedHashSet<>();
|
||||||
Set<String> libs = wrapperConfig.libraries;
|
Set<String> libs = wrapperConfig.libraries();
|
||||||
if (libs == null) libs = new LinkedHashSet<>();
|
if (libs == null) libs = new LinkedHashSet<>();
|
||||||
|
|
||||||
boolean configChanged = false;
|
boolean configChanged = false;
|
||||||
|
|
||||||
for (String lib : libs) {
|
for (String lib : libs) {
|
||||||
Path p = artifactToPath(lib);
|
Path p = ArtifactMeta.parse(lib).getLocalPath();
|
||||||
if (!Files.exists(p)) {
|
if (!Files.exists(p)) {
|
||||||
configChanged = true;
|
configChanged = true;
|
||||||
downloadLibrary(wrapperConfig.repositories, lib, libs);
|
downloadLibrary(wrapperConfig.repositories(), lib, libs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (String lib : natives) {
|
for (String lib : natives) {
|
||||||
Path p = artifactToPath(lib);
|
Path p = ArtifactMeta.parse(lib).getLocalPath();
|
||||||
if (!Files.exists(p)) {
|
if (!Files.exists(p)) {
|
||||||
configChanged = true;
|
configChanged = true;
|
||||||
downloadLibrary(wrapperConfig.repositories, lib, natives);
|
downloadLibrary(wrapperConfig.repositories(), lib, natives);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configChanged) JFiles.writeObject(MetaHolder.WRAPPER_CONFIG_PATH, wrapperConfig);
|
if (configChanged) GC_WrapperConfig.serialize(wrapperConfig, MetaHolder.WRAPPER_CONFIG_PATH, GsonPreset.CONFIG);
|
||||||
|
|
||||||
return buildClasspath(Stream.concat(libs.stream(), natives.stream())).toList();
|
return buildClasspath(Stream.concat(libs.stream(), natives.stream())).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Stream<Path> buildClasspath(Stream<String> libraries) {
|
private static Stream<Path> buildClasspath(Stream<String> libraries) {
|
||||||
return libraries.map(Updater::artifactToPath);
|
return libraries.map(ArtifactMeta::parse).map(ArtifactMeta::getLocalPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DependencyNode downloadLibrary(Set<String> repositories, final String artifact, Set<String> libraries) throws IOException, URISyntaxException {
|
private static DependencyNode downloadLibrary(Set<String> repositories, final String artifact, Set<String> libraries) throws IOException, URISyntaxException {
|
||||||
List<Exception> exceptions = new LinkedList<>();
|
List<FileNotFoundException> suppressed = new LinkedList<>();
|
||||||
for (String repository : Stream.concat(Stream.of(GitlabApi.PROJECT_MAVEN), repositories.stream()).toList()) {
|
for (String repository : Stream.concat(Stream.of(PROJECT_MAVEN), repositories.stream()).toList()) {
|
||||||
|
ArtifactMeta meta;
|
||||||
|
try {
|
||||||
|
meta = MavenApi.getMetadata(repository, artifact);
|
||||||
|
} catch (FileNotFoundException ignored) {
|
||||||
|
meta = ArtifactMeta.parse(artifact);
|
||||||
|
} catch (IOException | URISyntaxException | SAXException e) {
|
||||||
|
throw new IOException("Could not download artifact from " + repository, e);
|
||||||
|
}
|
||||||
Pom pom;
|
Pom pom;
|
||||||
try {
|
try {
|
||||||
pom = MavenApi.getPom(repository, artifact);
|
pom = MavenApi.getPom(repository, meta);
|
||||||
} catch (IOException | URISyntaxException | XMLStreamException | SAXException e) {
|
} catch (FileNotFoundException notFound) {
|
||||||
exceptions.add(new Exception("Could not download artifact from " + repository, e));
|
suppressed.add(notFound);
|
||||||
continue;
|
continue;
|
||||||
|
} catch (IOException | URISyntaxException | XMLStreamException | SAXException e) {
|
||||||
|
throw new IOException("Could not download artifact " + meta.getMavenNotation() + " from " + repository, e);
|
||||||
}
|
}
|
||||||
Set<DependencyNode> dependencies = new LinkedHashSet<>();
|
Set<DependencyNode> dependencies = new LinkedHashSet<>();
|
||||||
if (pom.dependencies != null) {
|
if (pom.dependencies() != null) {
|
||||||
for (MavenDependency dependency : pom.dependencies) {
|
for (MavenDependency dependency : pom.dependencies()) {
|
||||||
String mvnName = dependency.groupId + ":" + dependency.artifactId + ":" + dependency.version;
|
String mvnName = dependency.groupId() + ":" + dependency.artifactId() + ":" + dependency.version();
|
||||||
dependencies.add(downloadLibrary(repositories, mvnName, libraries));
|
dependencies.add(downloadLibrary(repositories, mvnName, libraries));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MavenApi.downloadLibrary(repository, artifact);
|
MavenApi.downloadLibrary(repository, meta);
|
||||||
libraries.add(artifact);
|
libraries.add(artifact);
|
||||||
return new DependencyNode(pom, dependencies);
|
return new DependencyNode(artifact, dependencies);
|
||||||
}
|
}
|
||||||
IOException exception = new IOException("Could not find any repository containing the artifact " + artifact + " (searched: " + String.join(", ", repositories) + ")");
|
IOException e = new IOException("Could not find any repository containing the artifact " + artifact + " (searched: " + String.join(", ", repositories) + ")");
|
||||||
for (Exception e : exceptions) {
|
for (FileNotFoundException ex : suppressed) e.addSuppressed(ex);
|
||||||
exception.addSuppressed(e);
|
throw e;
|
||||||
}
|
|
||||||
throw exception;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path artifactToPath(String artifact) {
|
public static @Nullable UpdateMetadata check(UpdateChannel channel, boolean versionCompare, boolean checkEnv, Consumer<UpdateChannel> channelInvalid) throws UpdateCheckException {
|
||||||
return MetaHolder.LIBRARIES_DIR.resolve(MavenApi.mavenNotationToJarPath(artifact)).toAbsolutePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @Nullable UpdateMetadata check(UpdateChannel channel, ComparableVersion current, Consumer<UpdateChannel> channelInvalid) {
|
|
||||||
try {
|
try {
|
||||||
int jvm = Runtime.version().feature();
|
UpdateMetadata experimental = Net.downloadObject(ARTIFACTS_URL + "version.json", json -> GC_UpdateMetadata.deserialize(json, GsonPreset.API));
|
||||||
GitlabProject project = GitlabApi.getProject(GitlabApi.PROJECT_ID);
|
|
||||||
UpdateMetadata experimental = null;
|
|
||||||
UpdateMetadata stable = null;
|
UpdateMetadata stable = null;
|
||||||
packageLoop:for (GitlabPackage info : GitlabApi.getPackages(project)) {
|
|
||||||
if (info.status.equals("default") && info.name.equals("io/gitlab/jfronny/inceptum/Inceptum")) {
|
|
||||||
pipelineLoop:for (GitlabPipeline pipeline : info.pipelines) {
|
|
||||||
if (!pipeline.ref.equals("master")) continue pipelineLoop;
|
|
||||||
if (!pipeline.status.equals("success")) {
|
|
||||||
Utils.LOGGER.warn("Skipping failed CI build");
|
|
||||||
continue pipelineLoop;
|
|
||||||
}
|
|
||||||
for (GitlabJob job : GitlabApi.getJobs(project, pipeline.id)) {
|
|
||||||
if (!job.name.equals("build_test")) continue;
|
|
||||||
try {
|
try {
|
||||||
UpdateMetadata update = Net.downloadObject(GitlabApi.PROJECTS + project.id + "/jobs/" + job.id + "/artifacts/version.json", UpdateMetadata.class);
|
stable = Net.downloadObject(STABLE_URL + "version.json", json -> GC_UpdateMetadata.deserialize(json, GsonPreset.API));
|
||||||
if (update.jvm > jvm) {
|
} catch (Throwable ignored) {}
|
||||||
Utils.LOGGER.error("A newer JVM is required to use the latest inceptum version. Please update!");
|
if (stable == null && channel == UpdateChannel.Stable) {
|
||||||
continue packageLoop;
|
|
||||||
}
|
|
||||||
if (experimental == null || experimental.version.compareTo(update.version) < 0) {
|
|
||||||
experimental = update;
|
|
||||||
}
|
|
||||||
if (!info.version.contains("-") && (stable == null || stable.version.compareTo(update.version) < 0)) {
|
|
||||||
stable = update;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
continue packageLoop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (experimental == null) {
|
|
||||||
throw new IOException("No version could be found");
|
|
||||||
} else if (stable == null && channel == UpdateChannel.Stable) {
|
|
||||||
channel = UpdateChannel.CI;
|
channel = UpdateChannel.CI;
|
||||||
channelInvalid.accept(channel);
|
channelInvalid.accept(channel);
|
||||||
}
|
}
|
||||||
@ -189,19 +155,39 @@ public class Updater {
|
|||||||
case CI -> experimental;
|
case CI -> experimental;
|
||||||
case Stable -> stable;
|
case Stable -> stable;
|
||||||
};
|
};
|
||||||
Utils.LOGGER.info("Latest version is " + info.version + ", current is " + current);
|
if (checkEnv) {
|
||||||
if (current.compareTo(info.version) >= 0) {
|
if (info.jvm() > Runtime.version().feature()) throw new UpdateCheckException("A newer JVM is required to use the latest inceptum version. Please update!", "Outdated Java");
|
||||||
|
if (info.wrapperVersion() != BuildMetadata.WRAPPER_VERSION) throw new UpdateCheckException("A different version of the Inceptum Wrapper is required for this update!", "Mismatched Wrapper");
|
||||||
|
}
|
||||||
|
if (versionCompare) {
|
||||||
|
Utils.LOGGER.info("Latest version is " + info.version() + ", current is " + BuildMetadata.VERSION);
|
||||||
|
if (BuildMetadata.BUILD_TIME >= info.buildTime()) {
|
||||||
Utils.LOGGER.info("Up-to-date");
|
Utils.LOGGER.info("Up-to-date");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return info;
|
return info;
|
||||||
} catch (IOException | URISyntaxException e) {
|
} catch (IOException e) {
|
||||||
Utils.LOGGER.error("Could not check for updates", e);
|
Utils.LOGGER.error("Could not check for updates", e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getShadowJarUrl(UpdateMetadata metadata) {
|
public static String getShadowJarUrl(UpdateChannel channel) {
|
||||||
return GitlabApi.PROJECT_MAVEN + "io/gitlab/jfronny/inceptum/Inceptum/" + metadata.version + "/Inceptum-" + metadata.version + "-" + Utils.getCurrentFlavor() + ".jar";
|
return switch (channel) {
|
||||||
|
case CI -> ARTIFACTS_URL;
|
||||||
|
case Stable -> STABLE_URL;
|
||||||
|
} + "/Inceptum-" + Utils.getCurrentFlavor() + ".jar";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UpdateCheckException extends Exception {
|
||||||
|
public final String message;
|
||||||
|
public final String title;
|
||||||
|
|
||||||
|
public UpdateCheckException(String message, String title) {
|
||||||
|
super(message);
|
||||||
|
this.message = message;
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,10 @@ package io.gitlab.jfronny.inceptum.common;
|
|||||||
|
|
||||||
import io.gitlab.jfronny.commons.OSUtils;
|
import io.gitlab.jfronny.commons.OSUtils;
|
||||||
import io.gitlab.jfronny.commons.io.JFiles;
|
import io.gitlab.jfronny.commons.io.JFiles;
|
||||||
import io.gitlab.jfronny.commons.log.Logger;
|
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.FileSystem;
|
import java.nio.file.FileSystem;
|
||||||
@ -17,8 +16,9 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
public static final int CACHE_SIZE = 128;
|
public static final int CACHE_SIZE = 128;
|
||||||
|
public static final Pattern NEW_LINE = Pattern.compile("[\r\n]+");
|
||||||
public static final Pattern VALID_FILENAME = Pattern.compile("[a-zA-Z0-9_\\-.][a-zA-Z0-9 _\\-.]*[a-zA-Z0-9_\\-.]");
|
public static final Pattern VALID_FILENAME = Pattern.compile("[a-zA-Z0-9_\\-.][a-zA-Z0-9 _\\-.]*[a-zA-Z0-9_\\-.]");
|
||||||
public static final Logger LOGGER = Logger.forName("Inceptum");
|
public static final SystemLoggerPlus LOGGER = SystemLoggerPlus.forName("Inceptum");
|
||||||
private static ClassLoader SYSTEM_LOADER = ClassLoader.getSystemClassLoader();
|
private static ClassLoader SYSTEM_LOADER = ClassLoader.getSystemClassLoader();
|
||||||
|
|
||||||
public static void openWebBrowser(URI uri) {
|
public static void openWebBrowser(URI uri) {
|
||||||
@ -41,12 +41,16 @@ public class Utils {
|
|||||||
Desktop.getDesktop().open(file);
|
Desktop.getDesktop().open(file);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Utils.LOGGER.error("Error opening web browser!", e);
|
Utils.LOGGER.error("Error opening file!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSystem openZipFile(Path zip, boolean create) throws IOException, URISyntaxException {
|
public static FileSystem openZipFile(Path zip, boolean create) throws IOException {
|
||||||
|
try {
|
||||||
return JFiles.openZipFile(zip, create, SYSTEM_LOADER);
|
return JFiles.openZipFile(zip, create, SYSTEM_LOADER);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new IOException("Could not access file system", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused") // Called through reflection from wrapper
|
@SuppressWarnings("unused") // Called through reflection from wrapper
|
||||||
@ -58,6 +62,7 @@ public class Utils {
|
|||||||
/**
|
/**
|
||||||
* Joins strings with the provided separator but removes separators from the start and end of the strings
|
* Joins strings with the provided separator but removes separators from the start and end of the strings
|
||||||
* Example: join('/', "some/path/", "/some/subpath/", "example/") -> "some/path/some/subpath/example
|
* Example: join('/', "some/path/", "/some/subpath/", "example/") -> "some/path/some/subpath/example
|
||||||
|
*
|
||||||
* @param separator The separator to join with
|
* @param separator The separator to join with
|
||||||
* @param segments The strings to join
|
* @param segments The strings to join
|
||||||
* @return The joined string
|
* @return The joined string
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.api;
|
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.HttpUtils;
|
|
||||||
import io.gitlab.jfronny.gson.reflect.TypeToken;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.model.gitlab.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public class GitlabApi {
|
|
||||||
public static final String PROJECTS = "https://gitlab.com/api/v4/projects/";
|
|
||||||
|
|
||||||
public static final long PROJECT_ID = 30862253L;
|
|
||||||
public static final String PROJECT_MAVEN = "https://gitlab.com/api/v4/projects/" + PROJECT_ID + "/packages/maven/";
|
|
||||||
|
|
||||||
private static final Type packageInfoListType = new TypeToken<List<GitlabPackage>>() {}.getType();
|
|
||||||
private static final Type jobListType = new TypeToken<List<GitlabJob>>() {}.getType();
|
|
||||||
private static final Type packageFileInfoListType = new TypeToken<List<GitlabPackageFile>>() {}.getType();
|
|
||||||
|
|
||||||
public static GitlabProject getProject(Long projectId) throws IOException, URISyntaxException {
|
|
||||||
return HttpUtils.get(PROJECTS + projectId).sendSerialized(GitlabProject.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<GitlabPackage> getPackages(GitlabProject project) throws IOException, URISyntaxException {
|
|
||||||
return HttpUtils.get(PROJECTS + project.id + "/packages?order_by=created_at&sort=desc").sendSerialized(packageInfoListType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<GitlabJob> getJobs(GitlabProject project, Long pipelineId) throws IOException, URISyntaxException {
|
|
||||||
List<GitlabJob> list = HttpUtils.get(PROJECTS + project.id + "/pipelines/" + pipelineId + "/jobs").sendSerialized(jobListType);
|
|
||||||
list.sort((left, right) -> right.created_at.compareTo(left.created_at));
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GitlabPackageFile getFile(GitlabProject project, GitlabPackage packageInfo, Predicate<GitlabPackageFile> isValid) throws IOException, URISyntaxException {
|
|
||||||
int page = 0;
|
|
||||||
while (true) {
|
|
||||||
List<GitlabPackageFile> files = HttpUtils.get(PROJECTS + project.id + "/packages/" + packageInfo.id + "/package_files?per_page=100&page=" + ++page).sendSerialized(packageFileInfoListType);
|
|
||||||
if (files.isEmpty()) return null;
|
|
||||||
for (GitlabPackageFile file : files) {
|
|
||||||
if (isValid.test(file))
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.api;
|
package io.gitlab.jfronny.inceptum.common.api;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.HttpUtils;
|
import io.gitlab.jfronny.commons.http.client.HttpClient;
|
||||||
import io.gitlab.jfronny.inceptum.common.*;
|
import io.gitlab.jfronny.inceptum.common.Net;
|
||||||
import io.gitlab.jfronny.inceptum.common.model.maven.MavenDependency;
|
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||||
import io.gitlab.jfronny.inceptum.common.model.maven.Pom;
|
import io.gitlab.jfronny.inceptum.common.model.maven.*;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.w3c.dom.*;
|
import org.w3c.dom.*;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
@ -28,125 +28,170 @@ public class MavenApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Path downloadLibrary(String repo, String artifact) throws IOException, URISyntaxException {
|
public static Path downloadLibrary(String repo, ArtifactMeta meta) throws IOException, URISyntaxException {
|
||||||
String path = mavenNotationToJarPath(artifact);
|
Path res = meta.getLocalPath();
|
||||||
Path res = MetaHolder.LIBRARIES_DIR.resolve(path);
|
Net.downloadFile(Utils.join("/", repo, meta.getJarPath(true)), res);
|
||||||
Net.downloadFile(Utils.join("/", repo, path), res);
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Pom getPom(String repo, String artifact) throws IOException, SAXException, URISyntaxException, XMLStreamException {
|
public static Pom getPom(String repo, ArtifactMeta meta) throws IOException, SAXException, URISyntaxException, XMLStreamException {
|
||||||
try (InputStream is = HttpUtils.get(Utils.join("/", repo, mavenNotationToPomPath(artifact))).sendInputStream()) {
|
try (InputStream is = HttpClient.get(Utils.join("/", repo, meta.getPomPath())).sendInputStream()) {
|
||||||
Document doc = FACTORY.parse(is);
|
Document doc = FACTORY.parse(is);
|
||||||
doc.getDocumentElement().normalize();
|
doc.getDocumentElement().normalize();
|
||||||
Pom result = new Pom();
|
|
||||||
if (!"project".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
|
if (!"project".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
|
||||||
boolean hasModelVersion = false;
|
String modelVersion = null;
|
||||||
boolean hasGroupId = false;
|
String groupId = null;
|
||||||
boolean hasArtifactId = false;
|
String artifactId = null;
|
||||||
boolean hasVersion = false;
|
String version = null;
|
||||||
for (Node node : iterable(doc.getDocumentElement().getChildNodes())) {
|
String packaging = null;
|
||||||
|
List<MavenDependency> dependencies = null;
|
||||||
|
String classifier = null;
|
||||||
|
for (Node node : children(doc.getDocumentElement())) {
|
||||||
switch (node.getNodeName()) {
|
switch (node.getNodeName()) {
|
||||||
case "modelVersion" -> {
|
case "modelVersion" -> modelVersion = node.getTextContent();
|
||||||
hasModelVersion = true;
|
|
||||||
result.modelVersion = node.getTextContent();
|
|
||||||
}
|
|
||||||
case "parent" -> {
|
case "parent" -> {
|
||||||
// Dirty hack to get slf4j working: simply assume the groupId and version of the parent is also the groupId of this
|
// Dirty hack to get slf4j working: simply assume the groupId and version of the parent is also the groupId of this
|
||||||
if (!hasGroupId) {
|
if (groupId == null) {
|
||||||
for (Node child : iterable(node.getChildNodes())) {
|
for (Node child : children(node)) {
|
||||||
switch (child.getNodeName()) {
|
switch (child.getNodeName()) {
|
||||||
case "groupId" -> {
|
case "groupId" -> {
|
||||||
if (!hasGroupId) {
|
if (groupId == null) {
|
||||||
hasGroupId = true;
|
groupId = child.getTextContent();
|
||||||
result.groupId = node.getTextContent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "version" -> {
|
case "version" -> {
|
||||||
if (!hasVersion) {
|
if (version == null) {
|
||||||
hasVersion = true;
|
version = child.getTextContent();
|
||||||
result.version = node.getTextContent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "groupId" -> {
|
case "groupId" -> groupId = node.getTextContent();
|
||||||
hasGroupId = true;
|
|
||||||
result.groupId = node.getTextContent();
|
|
||||||
}
|
|
||||||
case "artifactId" -> {
|
case "artifactId" -> {
|
||||||
hasArtifactId = true;
|
artifactId = node.getTextContent();
|
||||||
result.artifactId = node.getTextContent();
|
|
||||||
}
|
}
|
||||||
case "version" -> {
|
case "version" -> {
|
||||||
hasVersion = true;
|
version = node.getTextContent();
|
||||||
result.version = node.getTextContent();
|
|
||||||
}
|
}
|
||||||
case "packaging" -> result.packaging = node.getTextContent();
|
case "packaging" -> packaging = node.getTextContent();
|
||||||
case "dependencies" -> {
|
case "dependencies" -> {
|
||||||
result.dependencies = new LinkedList<>();
|
dependencies = new LinkedList<>();
|
||||||
for (Node dep : iterable(node.getChildNodes())) {
|
for (Node dep : children(node)) {
|
||||||
MavenDependency resolved = parseDependency(dep);
|
MavenDependency resolved = parseDependency(dep);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
result.dependencies.add(resolved);
|
dependencies.add(resolved);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "classifier" -> classifier = node.getTextContent();
|
||||||
default -> {}
|
default -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasModelVersion) throw new IOException("Pom lacks modelVersion");
|
if (modelVersion == null) throw new IOException("Pom lacks modelVersion");
|
||||||
if (!hasGroupId) throw new IOException("Pom lacks groupId");
|
if (groupId == null) throw new IOException("Pom lacks groupId");
|
||||||
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
|
if (artifactId == null) throw new IOException("Pom lacks artifactId");
|
||||||
if (!hasVersion) throw new IOException("Pom lacks version");
|
if (version == null) throw new IOException("Pom lacks version");
|
||||||
return result;
|
return new Pom(modelVersion, groupId, artifactId, version, classifier, null, packaging, dependencies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable MavenDependency parseDependency(Node doc) throws IOException {
|
private static @Nullable MavenDependency parseDependency(Node doc) throws IOException {
|
||||||
MavenDependency result = new MavenDependency();
|
String groupId = null;
|
||||||
boolean hasGroupId = false;
|
String artifactId = null;
|
||||||
boolean hasArtifactId = false;
|
String version = null;
|
||||||
boolean hasVersion = false;
|
String scope = null;
|
||||||
boolean hasScope = false;
|
for (Node node : children(doc)) {
|
||||||
for (Node node : iterable(doc.getChildNodes())) {
|
|
||||||
switch (node.getNodeName()) {
|
switch (node.getNodeName()) {
|
||||||
case "groupId" -> {
|
case "groupId" -> groupId = node.getTextContent();
|
||||||
hasGroupId = true;
|
case "artifactId" -> artifactId = node.getTextContent();
|
||||||
result.groupId = node.getTextContent();
|
case "version" -> version = node.getTextContent();
|
||||||
}
|
|
||||||
case "artifactId" -> {
|
|
||||||
hasArtifactId = true;
|
|
||||||
result.artifactId = node.getTextContent();
|
|
||||||
}
|
|
||||||
case "version" -> {
|
|
||||||
hasVersion = true;
|
|
||||||
result.version = node.getTextContent();
|
|
||||||
}
|
|
||||||
case "scope" -> {
|
case "scope" -> {
|
||||||
hasScope = true;
|
scope = node.getTextContent();
|
||||||
result.scope = node.getTextContent();
|
if (!RUNTIME_SCOPES.contains(scope)) return null;
|
||||||
if (!RUNTIME_SCOPES.contains(result.scope)) return null;
|
|
||||||
}
|
}
|
||||||
case "optional" -> {
|
case "optional" -> {
|
||||||
if (node.getTextContent().equals("true")) return null;
|
if (node.getTextContent().equals("true")) return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!hasGroupId) throw new IOException("Pom lacks groupId");
|
if (groupId == null) throw new IOException("Pom lacks groupId");
|
||||||
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
|
if (artifactId == null) throw new IOException("Pom lacks artifactId");
|
||||||
if (!hasVersion) {
|
if (version == null) {
|
||||||
if (result.groupId.equals("org.lwjgl")) {
|
if (groupId.equals("org.lwjgl")) {
|
||||||
// Lwjgl uses a shared bom for versions which I don't want to support
|
// Lwjgl uses a shared bom for versions which I don't want to support
|
||||||
// The required modules are explicit dependencies of launcher-imgui anyway
|
// The required modules are explicit dependencies of launcher-imgui anyway
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
throw new IOException("Dependency " + result.groupId + ":" + result.artifactId + " lacks version");
|
throw new IOException("Dependency " + groupId + ":" + artifactId + " lacks version");
|
||||||
}
|
}
|
||||||
if (!hasScope) throw new IOException("Pom lacks scope");
|
if (scope == null) throw new IOException("Pom lacks scope");
|
||||||
return result;
|
return new MavenDependency(groupId, artifactId, version, scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArtifactMeta getMetadata(String repo, String artifact) throws IOException, SAXException, URISyntaxException {
|
||||||
|
ArtifactMeta sourceMeta = ArtifactMeta.parse(artifact);
|
||||||
|
try (InputStream is = HttpClient.get(Utils.join("/", repo, sourceMeta.getMetadataPath())).sendInputStream()) {
|
||||||
|
Document doc = FACTORY.parse(is);
|
||||||
|
doc.getDocumentElement().normalize();
|
||||||
|
if (!"metadata".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
|
||||||
|
String groupId = null;
|
||||||
|
String artifactId = null;
|
||||||
|
String version = null;
|
||||||
|
String snapshotVersion = null;
|
||||||
|
for (Node node : children(doc.getDocumentElement())) {
|
||||||
|
switch (node.getNodeName()) {
|
||||||
|
case "groupId" -> groupId = node.getTextContent();
|
||||||
|
case "artifactId" -> artifactId = node.getTextContent();
|
||||||
|
case "version" -> version = node.getTextContent();
|
||||||
|
case "versioning" -> {
|
||||||
|
for (Node node1 : children(node)) {
|
||||||
|
if (node1.getNodeName().equals("snapshot")) {
|
||||||
|
String timestamp = null;
|
||||||
|
String buildNumber = null;
|
||||||
|
for (Node node2 : children(node1)) {
|
||||||
|
switch (node2.getNodeName()) {
|
||||||
|
case "timestamp" -> timestamp = node2.getTextContent();
|
||||||
|
case "buildNumber" -> buildNumber = node2.getTextContent();
|
||||||
|
default -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (timestamp == null) throw new IOException("Pom snapshots lack timestamp");
|
||||||
|
if (buildNumber == null) throw new IOException("Pom snapshots lack buildNumber");
|
||||||
|
snapshotVersion = timestamp + '-' + buildNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (groupId == null) throw new IOException("Pom lacks groupId");
|
||||||
|
if (artifactId == null) throw new IOException("Pom lacks artifactId");
|
||||||
|
if (version == null) throw new IOException("Pom lacks version");
|
||||||
|
return new ArtifactMeta(groupId, artifactId, version, sourceMeta.classifier(), snapshotVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Iterable<Node> children(Node node) {
|
||||||
|
return () -> new Iterator<Node>() {
|
||||||
|
NodeList children = node.getChildNodes();
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
while (index < children.getLength() && isWhitespace(children.item(index))) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return index < children.getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node next() {
|
||||||
|
if (!hasNext()) throw new NoSuchElementException();
|
||||||
|
return children.item(index++);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isWhitespace(Node node) {
|
private static boolean isWhitespace(Node node) {
|
||||||
@ -154,67 +199,4 @@ public class MavenApi {
|
|||||||
if (node.getNodeType() == Node.COMMENT_NODE) return true;
|
if (node.getNodeType() == Node.COMMENT_NODE) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Iterable<Node> iterable(NodeList list) {
|
|
||||||
return () -> new Iterator<>() {
|
|
||||||
int index = 0;
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
while (index < list.getLength() && isWhitespace(list.item(index))) {
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
return index < list.getLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node next() {
|
|
||||||
if (!hasNext()) throw new NoSuchElementException();
|
|
||||||
return list.item(index++);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an artifact in maven notation to a jar file path. The following are supported:
|
|
||||||
* - some.base.path:artifact:version -> some/base/path/artifact/version/artifact-version.jar
|
|
||||||
* - some.base.path:artifact:version:classifier -> some/base/path/artifact/version/artifact-version-classifier.jar
|
|
||||||
* @param mavenNotation An artifact in maven notation
|
|
||||||
* @return A file path
|
|
||||||
*/
|
|
||||||
public static String mavenNotationToJarPath(String mavenNotation) {
|
|
||||||
if (Objects.requireNonNull(mavenNotation).isEmpty()) throw new IllegalArgumentException("The notation is empty");
|
|
||||||
String[] lib = mavenNotation.split(":");
|
|
||||||
if (lib.length <= 1) throw new IllegalArgumentException("Not in maven notation");
|
|
||||||
if (lib.length == 2) throw new IllegalArgumentException("Skipping versions is not supported");
|
|
||||||
if (lib.length >= 5) throw new IllegalArgumentException("Unkown elements in maven notation");
|
|
||||||
String path = lib[0].replace('.', '/') + '/'; // Base
|
|
||||||
path += lib[1] + '/'; // Artifact name
|
|
||||||
path += lib[2] + '/'; // Version
|
|
||||||
if (lib.length == 3) { // artifact-version.jar
|
|
||||||
path += lib[1] + '-' + lib[2];
|
|
||||||
} else { // artifact-version-classifier.jar
|
|
||||||
path += lib[1] + '-' + lib[2] + "-" + lib[3];
|
|
||||||
}
|
|
||||||
return path + ".jar";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an artifact in maven notation to a pom file path. The following are supported:
|
|
||||||
* - some.base.path:artifact:version -> some/base/path/artifact/version/artifact-version.pom
|
|
||||||
* - some.base.path:artifact:version:classifier -> some/base/path/artifact/version/artifact-version.pom
|
|
||||||
* @param mavenNotation An artifact in maven notation
|
|
||||||
* @return A file path
|
|
||||||
*/
|
|
||||||
public static String mavenNotationToPomPath(String mavenNotation) {
|
|
||||||
if (Objects.requireNonNull(mavenNotation).isEmpty()) throw new IllegalArgumentException("The notation is empty");
|
|
||||||
String[] lib = mavenNotation.split(":");
|
|
||||||
if (lib.length <= 1) throw new IllegalArgumentException("Not in maven notation");
|
|
||||||
if (lib.length == 2) throw new IllegalArgumentException("Skipping versions is not supported");
|
|
||||||
if (lib.length >= 5) throw new IllegalArgumentException("Unkown elements in maven notation");
|
|
||||||
String path = lib[0].replace('.', '/') + '/'; // Base
|
|
||||||
path += lib[1] + '/'; // Artifact name
|
|
||||||
path += lib[2] + '/'; // Version
|
|
||||||
path += lib[1] + '-' + lib[2]; // artifact-version
|
|
||||||
return path + ".pom";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.gitlab;
|
|
||||||
|
|
||||||
public class GitlabArtifact {
|
|
||||||
public String file_type;
|
|
||||||
public Long size;
|
|
||||||
public String filename;
|
|
||||||
public String file_format;
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.gitlab;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GitlabCommit {
|
|
||||||
public String id;
|
|
||||||
public String short_id;
|
|
||||||
public Date created_at;
|
|
||||||
public List<String> parent_ids;
|
|
||||||
public String title;
|
|
||||||
public String message;
|
|
||||||
public String author_name;
|
|
||||||
public String author_email;
|
|
||||||
public Date authored_date;
|
|
||||||
public String committer_name;
|
|
||||||
public String committer_email;
|
|
||||||
public Date committed_date;
|
|
||||||
public String web_url;
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.gitlab;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GitlabJob {
|
|
||||||
public Long id;
|
|
||||||
public String status;
|
|
||||||
public String stage;
|
|
||||||
public String name;
|
|
||||||
public String ref;
|
|
||||||
public Boolean tag;
|
|
||||||
public Boolean allow_failure;
|
|
||||||
public Date created_at;
|
|
||||||
public Date started_at;
|
|
||||||
public Date finished_at;
|
|
||||||
public Double duration;
|
|
||||||
public Double queued_duration;
|
|
||||||
public GitlabUser user;
|
|
||||||
public GitlabCommit commit;
|
|
||||||
public GitlabPipeline pipeline;
|
|
||||||
public String web_url;
|
|
||||||
public List<GitlabArtifact> artifacts;
|
|
||||||
public GitlabRunner runner;
|
|
||||||
public Date artifacts_expire_at;
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.gitlab;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GitlabPackage {
|
|
||||||
public Long id;
|
|
||||||
public String name;
|
|
||||||
public String version;
|
|
||||||
public String package_type;
|
|
||||||
public String status;
|
|
||||||
public Date created_at;
|
|
||||||
public List<GitlabPipeline> pipelines;
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.gitlab;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GitlabPackageFile {
|
|
||||||
public Long id;
|
|
||||||
public Long package_id;
|
|
||||||
public Date created_at;
|
|
||||||
public String file_name;
|
|
||||||
public Integer size;
|
|
||||||
public String file_md5;
|
|
||||||
public String file_sha1;
|
|
||||||
public String file_sha256;
|
|
||||||
public List<GitlabPipeline> pipelines;
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.gitlab;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class GitlabPipeline {
|
|
||||||
public long id;
|
|
||||||
public long project_id;
|
|
||||||
public String sha;
|
|
||||||
public String ref;
|
|
||||||
public String status;
|
|
||||||
public String source;
|
|
||||||
public Date created_at;
|
|
||||||
public Date updated_at;
|
|
||||||
public String web_url;
|
|
||||||
public GitlabUser user;
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.gitlab;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class GitlabProject {
|
|
||||||
public Long id;
|
|
||||||
public String description;
|
|
||||||
public String name;
|
|
||||||
public String name_with_namespace;
|
|
||||||
public String path;
|
|
||||||
public String path_with_namespace;
|
|
||||||
public String created_at;
|
|
||||||
public String default_branch;
|
|
||||||
public String ssh_url_to_repo;
|
|
||||||
public String http_url_to_repo;
|
|
||||||
public String web_url;
|
|
||||||
public String readme_url;
|
|
||||||
public String avatar_url;
|
|
||||||
public Integer forks_count;
|
|
||||||
public Integer star_count;
|
|
||||||
public Date last_activity_at;
|
|
||||||
public Boolean packages_enabled;
|
|
||||||
public Boolean empty_repo;
|
|
||||||
public Boolean archived;
|
|
||||||
public String visibility;
|
|
||||||
public GitlabUser owner;
|
|
||||||
public Boolean resolve_outdated_diff_discussions;
|
|
||||||
public Boolean issues_enabled;
|
|
||||||
public Boolean merge_requests_enabled;
|
|
||||||
public Boolean wiki_enabled;
|
|
||||||
public Boolean jobs_enabled;
|
|
||||||
public Boolean snippets_enabled;
|
|
||||||
public Boolean container_registry_enabled;
|
|
||||||
public Boolean service_desk_enabled;
|
|
||||||
public String service_desk_address;
|
|
||||||
public Boolean can_create_merge_request_in;
|
|
||||||
public String issues_access_level;
|
|
||||||
public String repository_access_level;
|
|
||||||
public String merge_request_access_level;
|
|
||||||
public String forking_access_level;
|
|
||||||
public String wiki_access_level;
|
|
||||||
public String builds_access_level;
|
|
||||||
public String snippets_access_level;
|
|
||||||
public String pages_access_level;
|
|
||||||
public String operations_access_level;
|
|
||||||
public String analytics_access_level;
|
|
||||||
public String container_registry_access_level;
|
|
||||||
public Boolean emails_disabled;
|
|
||||||
public Boolean shared_runners_enabled;
|
|
||||||
public Boolean lfs_enabled;
|
|
||||||
public Long creator_id;
|
|
||||||
public String import_status;
|
|
||||||
public Integer open_issues_count;
|
|
||||||
public String runners_token;
|
|
||||||
public Integer ci_default_git_depth;
|
|
||||||
public Boolean ci_forward_deployment_enabled;
|
|
||||||
public Boolean ci_job_token_scope_enabled;
|
|
||||||
public Boolean public_jobs;
|
|
||||||
public String build_git_strategy;
|
|
||||||
public Integer build_timeout;
|
|
||||||
public String auto_cancel_pending_pipelines;
|
|
||||||
public String build_coverage_regex;
|
|
||||||
public String ci_config_path;
|
|
||||||
public Boolean only_allow_merge_if_pipeline_succeeds;
|
|
||||||
public Boolean restrict_user_defined_variables;
|
|
||||||
public Boolean request_access_enabled;
|
|
||||||
public Boolean only_allow_merge_if_all_discussions_are_resolved;
|
|
||||||
public Boolean remove_source_branch_after_merge;
|
|
||||||
public Boolean printing_merge_request_link_enabled;
|
|
||||||
public String merge_method;
|
|
||||||
public String squash_option;
|
|
||||||
public String suggestion_commit_message;
|
|
||||||
public Boolean auto_devops_enabled;
|
|
||||||
public String auto_devops_strategy;
|
|
||||||
public Boolean autoclose_referenced_issues;
|
|
||||||
public Boolean keep_latest_artifact;
|
|
||||||
public Integer approvals_before_merge;
|
|
||||||
public Boolean mirror;
|
|
||||||
public String external_authorization_classification_label;
|
|
||||||
public Integer requirements_enabled;
|
|
||||||
public Integer security_and_compliance_enabled;
|
|
||||||
public String issues_template;
|
|
||||||
public String merge_requests_template;
|
|
||||||
public Boolean merge_pipelines_enabled;
|
|
||||||
public Boolean merge_trains_enabled;
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.gitlab;
|
|
||||||
|
|
||||||
public class GitlabRunner {
|
|
||||||
public Long id;
|
|
||||||
public String description;
|
|
||||||
public String ip_address;
|
|
||||||
public Boolean active;
|
|
||||||
public Boolean is_shared;
|
|
||||||
public String runner_type;
|
|
||||||
public String name;
|
|
||||||
public Boolean online;
|
|
||||||
public String deprecated_rest_status;
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.gitlab;
|
|
||||||
|
|
||||||
public class GitlabUser {
|
|
||||||
public long id;
|
|
||||||
public String name;
|
|
||||||
public String username;
|
|
||||||
public String state;
|
|
||||||
public String avatar_url;
|
|
||||||
public String web_url;
|
|
||||||
}
|
|
@ -1,15 +1,18 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.ComparableVersion;
|
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
|
||||||
|
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class UpdateMetadata {
|
@GSerializable
|
||||||
public Integer wrapperVersion;
|
public record UpdateMetadata(int wrapperVersion,
|
||||||
public ComparableVersion version;
|
String version,
|
||||||
public Boolean isPublic;
|
long buildTime,
|
||||||
public Boolean isRelease;
|
boolean isPublic,
|
||||||
public Integer jvm;
|
boolean isRelease,
|
||||||
public Set<String> repositories;
|
int jvm,
|
||||||
public Map<String, Set<String>> natives;
|
Set<String> repositories,
|
||||||
|
Map<String, Set<String>> natives) {
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
||||||
|
|
||||||
import java.util.*;
|
import io.gitlab.jfronny.commons.serialize.generator.annotations.GSerializable;
|
||||||
|
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||||
|
|
||||||
public class WrapperConfig {
|
import java.util.Map;
|
||||||
public Set<String> libraries;
|
import java.util.Set;
|
||||||
public Set<String> repositories;
|
|
||||||
public Map<String, Set<String>> natives;
|
@GSerializable
|
||||||
|
public record WrapperConfig(Set<String> libraries, Set<String> repositories, Map<String, Set<String>> natives) {
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.common.model.maven;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record ArtifactMeta(String groupId, String artifactId, String version, @Nullable String classifier, @Nullable String snapshotVersion) {
|
||||||
|
public ArtifactMeta(String groupId, String artifactId, String version) {
|
||||||
|
this(groupId, artifactId, version, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArtifactMeta parse(String mavenNotation) {
|
||||||
|
if (Objects.requireNonNull(mavenNotation).isEmpty()) throw new IllegalArgumentException("The notation is empty");
|
||||||
|
String[] lib = mavenNotation.split(":");
|
||||||
|
if (lib.length <= 1) throw new IllegalArgumentException("Not in maven notation");
|
||||||
|
if (lib.length == 2) throw new IllegalArgumentException("Skipping versions is not supported");
|
||||||
|
if (lib.length >= 5) throw new IllegalArgumentException("Unkown elements in maven notation");
|
||||||
|
return new ArtifactMeta(lib[0], lib[1], lib[2], lib.length > 3 ? lib[3] : null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPomPath() {
|
||||||
|
String path = groupId.replace('.', '/') + '/';
|
||||||
|
path += artifactId + '/';
|
||||||
|
path += version + '/';
|
||||||
|
path += artifactId + '-';
|
||||||
|
if (snapshotVersion != null) path += version.replace("SNAPSHOT", snapshotVersion);
|
||||||
|
else path += version;
|
||||||
|
return path + ".pom";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJarPath(boolean respectSnapshotVersion) {
|
||||||
|
String path = groupId.replace('.', '/') + '/';
|
||||||
|
path += artifactId + '/';
|
||||||
|
path += version + '/';
|
||||||
|
path += artifactId + '-';
|
||||||
|
if (snapshotVersion != null && respectSnapshotVersion) path += version.replace("SNAPSHOT", snapshotVersion);
|
||||||
|
else path += version;
|
||||||
|
if (classifier != null) path += '-' + classifier;
|
||||||
|
return path + ".jar";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMavenNotation() {
|
||||||
|
String notation = groupId + ':' + artifactId + ':' + version;
|
||||||
|
if (classifier != null) notation += ':' + classifier;
|
||||||
|
return notation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMetadataPath() {
|
||||||
|
return groupId.replace('.', '/') + '/' + artifactId + '/' + version + "/maven-metadata.xml";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getLocalPath() {
|
||||||
|
return MetaHolder.LIBRARIES_DIR.resolve(getJarPath(false));
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +1,9 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.maven;
|
package io.gitlab.jfronny.inceptum.common.model.maven;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
public class DependencyNode {
|
|
||||||
private final String name;
|
|
||||||
private final Set<DependencyNode> dependencies;
|
|
||||||
|
|
||||||
public DependencyNode(Pom pom, Set<DependencyNode> dependencies) {
|
|
||||||
Objects.requireNonNull(pom);
|
|
||||||
this.name = pom.groupId + ":" + pom.artifactId + ":" + pom.version;
|
|
||||||
this.dependencies = Objects.requireNonNull(dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
DependencyNode that = (DependencyNode) o;
|
|
||||||
return name.equals(that.name) && dependencies.equals(that.dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(name, dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public record DependencyNode(String name, Set<DependencyNode> dependencies) {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@ -34,7 +13,7 @@ public class DependencyNode {
|
|||||||
|
|
||||||
private void generateTree(StringBuilder sb, String prefix, String childrenPrefix) {
|
private void generateTree(StringBuilder sb, String prefix, String childrenPrefix) {
|
||||||
sb.append(prefix).append(name).append('\n');
|
sb.append(prefix).append(name).append('\n');
|
||||||
for (Iterator<DependencyNode> it = dependencies.iterator(); it.hasNext();) {
|
for (Iterator<DependencyNode> it = dependencies.iterator(); it.hasNext(); ) {
|
||||||
DependencyNode next = it.next();
|
DependencyNode next = it.next();
|
||||||
if (it.hasNext()) {
|
if (it.hasNext()) {
|
||||||
next.generateTree(sb, childrenPrefix + "├── ", childrenPrefix + "│ ");
|
next.generateTree(sb, childrenPrefix + "├── ", childrenPrefix + "│ ");
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
package io.gitlab.jfronny.inceptum.common.model.maven;
|
package io.gitlab.jfronny.inceptum.common.model.maven;
|
||||||
|
|
||||||
public class MavenDependency {
|
public record MavenDependency(String groupId, String artifactId, String version, String scope) {
|
||||||
public String groupId;
|
|
||||||
public String artifactId;
|
|
||||||
public String version;
|
|
||||||
public String scope;
|
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,12 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Pom {
|
public record Pom(String modelVersion,
|
||||||
public String modelVersion;
|
String groupId,
|
||||||
public String groupId;
|
String artifactId,
|
||||||
public String artifactId;
|
String version,
|
||||||
public String version;
|
String classifier,
|
||||||
@Nullable public String packaging;
|
String snapshotVersion,
|
||||||
@Nullable public List<MavenDependency> dependencies;
|
@Nullable String packaging,
|
||||||
|
@Nullable List<MavenDependency> dependencies) {
|
||||||
}
|
}
|
||||||
|
17
common/src/main/java/module-info.java
Normal file
17
common/src/main/java/module-info.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module io.gitlab.jfronny.inceptum.common {
|
||||||
|
exports io.gitlab.jfronny.inceptum.common;
|
||||||
|
exports io.gitlab.jfronny.inceptum.common.api;
|
||||||
|
exports io.gitlab.jfronny.inceptum.common.model.inceptum;
|
||||||
|
exports io.gitlab.jfronny.inceptum.common.model.maven;
|
||||||
|
|
||||||
|
requires transitive java.desktop;
|
||||||
|
requires java.xml;
|
||||||
|
requires transitive io.gitlab.jfronny.commons;
|
||||||
|
requires transitive io.gitlab.jfronny.commons.http.client;
|
||||||
|
requires transitive io.gitlab.jfronny.commons.io;
|
||||||
|
requires transitive io.gitlab.jfronny.commons.logger;
|
||||||
|
requires transitive io.gitlab.jfronny.commons.serialize.json;
|
||||||
|
requires static org.jetbrains.annotations;
|
||||||
|
requires static io.gitlab.jfronny.commons.serialize.generator.annotations;
|
||||||
|
requires io.gitlab.jfronny.commons.serialize;
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
# CLI
|
# CLI
|
||||||
Inceptum provides a CLI which performs similar functions to the GUI. If you have feature requests, [open an issue](https://gitlab.com/jfmods/inceptum/-/issues)
|
Inceptum provides a CLI which performs similar functions to the GUI. If you have feature requests, [open an issue](https://helpdesk.frohnmeyer-wds.de/issue?repo=Inceptum&owner=JfMods)
|
||||||
To view up-to-date information on the commands provided by Inceptum, run `inceptum help` or `inceptum help <command>`, this page is intended to explain more advanced features
|
To view up-to-date information on the commands provided by Inceptum, run `inceptum help` or `inceptum help <command>`, this page is intended to explain more advanced features
|
||||||
|
|
||||||
## The inceptum wrapper
|
## The inceptum wrapper
|
||||||
|
|
||||||
Inceptum Wrapper looks through the libraries dir and launches the latest available Inceptum version.
|
Inceptum Wrapper looks through the libraries dir and launches the latest available Inceptum version.
|
||||||
If it doesn't find a usable version, it will download the latest from GitLab.
|
If it doesn't find a usable version, it will download the latest available version automatically.
|
||||||
Launching is performed through a custom ClassLoader and an internal flag is set to inform the launched version about the wrappers' presence.
|
Launching is performed through a custom ClassLoader and an internal flag is set to inform the launched version about the wrappers' presence.
|
||||||
|
|
||||||
## The "batch" command
|
## The "batch" command
|
||||||
|
@ -5,7 +5,7 @@ All of these are subject to change, though automatic migrations will likely be p
|
|||||||
|
|
||||||
## inceptum.json (Main Config)
|
## inceptum.json (Main Config)
|
||||||
|
|
||||||
```json
|
```json5
|
||||||
{
|
{
|
||||||
// Whether to show snapshots in the version selector for new instances
|
// Whether to show snapshots in the version selector for new instances
|
||||||
"snapshots": false,
|
"snapshots": false,
|
||||||
@ -38,7 +38,7 @@ It stores your minecraft account login and CAN BE USED TO IMPERSONATE YOU!
|
|||||||
|
|
||||||
Please note that all entries except for "version" are optional
|
Please note that all entries except for "version" are optional
|
||||||
|
|
||||||
```json
|
```json5
|
||||||
{
|
{
|
||||||
// The version to use for launching this
|
// The version to use for launching this
|
||||||
// Can be a fabric loader version (as seen here) or a normal minecraft version (like "1.17.1")
|
// Can be a fabric loader version (as seen here) or a normal minecraft version (like "1.17.1")
|
||||||
@ -74,7 +74,7 @@ Please note that all entries except for "version" are optional
|
|||||||
|
|
||||||
## *.imod (Mod Metadata)
|
## *.imod (Mod Metadata)
|
||||||
|
|
||||||
```json
|
```json5
|
||||||
{
|
{
|
||||||
// Where the JAR file for this mod can be obtained
|
// Where the JAR file for this mod can be obtained
|
||||||
"sources": [
|
"sources": [
|
||||||
@ -121,6 +121,8 @@ Please note that all entries except for "version" are optional
|
|||||||
// A list of dependencies by their file names
|
// A list of dependencies by their file names
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"someOtherMod.imod"
|
"someOtherMod.imod"
|
||||||
]
|
],
|
||||||
|
// Whether this mod was explicitly installed (used during mod removal)
|
||||||
|
"explicit": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -6,11 +6,11 @@ Inceptum can be installed in a number of ways, all of which are documented below
|
|||||||
|
|
||||||
First, download the Inceptum build appropriate for your system:
|
First, download the Inceptum build appropriate for your system:
|
||||||
|
|
||||||
- [Windows x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum-windows.jar?job=build_platform_jars)
|
- [Windows x86_64](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/Inceptum-windows.jar)
|
||||||
- [MacOS x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum-macos.jar?job=build_platform_jars) (untested)
|
- [MacOS x86_64](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/Inceptum-macos.jar) (untested)
|
||||||
- [Linux x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum-linux.jar?job=build_platform_jars)
|
- [Linux x86_64](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/Inceptum-linux.jar)
|
||||||
|
|
||||||
You can also download a [single jar](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum.jar?job=build_platform_jars)
|
You can also download a [single jar](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/Inceptum.jar)
|
||||||
that contains support for all of these systems (though doing so is not recommended)
|
that contains support for all of these systems (though doing so is not recommended)
|
||||||
|
|
||||||
Once you have a jar, run it with an [up-to-date java version](https://adoptium.net/).
|
Once you have a jar, run it with an [up-to-date java version](https://adoptium.net/).
|
||||||
@ -32,10 +32,10 @@ If this parameter is specified, all other locations will be ignored.
|
|||||||
## Simple installation with updates
|
## Simple installation with updates
|
||||||
|
|
||||||
To use automatic updates, you must use the Inceptum Wrapper.
|
To use automatic updates, you must use the Inceptum Wrapper.
|
||||||
Simply launch this [cross-platform jar](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/wrapper.jar?job=build_wrapper)
|
Simply launch this [cross-platform jar](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/wrapper.jar)
|
||||||
and Inceptum will launch as described above (though the initial startup may take a bit longer). The same rules for config locations apply.
|
and Inceptum will launch as described above (though the initial startup may take a bit longer). The same rules for config locations apply.
|
||||||
|
|
||||||
You may also download the [windows exe](https://gitlab.com/jfmods/inceptum/-/jobs/artifacts/master/raw/wrapper.exe?job=build_wrapper)
|
You may also download the [windows exe](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/wrapper.exe)
|
||||||
which uses fabric-installer-native-bootstrap to locate the JVM used by the official minecraft launcher and launch Inceptum using that.
|
which uses fabric-installer-native-bootstrap to locate the JVM used by the official minecraft launcher and launch Inceptum using that.
|
||||||
Please be aware that this is pretty much untested
|
Please be aware that this is pretty much untested
|
||||||
|
|
||||||
@ -48,6 +48,6 @@ For more information on the syntax of this services config file, go [here](Comma
|
|||||||
|
|
||||||
## Using Inceptum on Windows without OpenGL drivers
|
## Using Inceptum on Windows without OpenGL drivers
|
||||||
|
|
||||||
Download the portable build from [here](https://gitlab.com/jfmods/inceptum/-/jobs/artifacts/master/raw/portable.7z?job=portable)
|
Download the portable build from [here](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/portable.7z)
|
||||||
This archive includes Inceptum using the Inceptum wrapper, a JVM and a Mesa build for CPU-based graphics.
|
This archive includes Inceptum using the Inceptum wrapper, a JVM and a Mesa build for CPU-based graphics.
|
||||||
Please be aware that using this WILL result in worse performance.
|
Please be aware that using this WILL result in worse performance.
|
51
gradle/libs.versions.toml
Normal file
51
gradle/libs.versions.toml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
[versions]
|
||||||
|
jf-commons = "2.0.0-SNAPSHOT"
|
||||||
|
annotations = "24.0.1"
|
||||||
|
lwjgl = "3.3.2"
|
||||||
|
imgui = "1.86.10"
|
||||||
|
javagi = "0.10.2"
|
||||||
|
kotlin = "2.0.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
plugin-shadow = "gradle.plugin.com.github.johnrengelman:shadow:7.1.2"
|
||||||
|
plugin-download = "de.undercouch:gradle-download-task:5.1.2"
|
||||||
|
plugin-jf-convention = "io.gitlab.jfronny:convention:1.5-SNAPSHOT"
|
||||||
|
plugin-jlink = "org.beryx:badass-jlink-plugin:3.0.1"
|
||||||
|
|
||||||
|
lwjgl-core = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" }
|
||||||
|
lwjgl-glfw = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl" }
|
||||||
|
lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" }
|
||||||
|
lwjgl-tinyfd = { module = "org.lwjgl:lwjgl-tinyfd", version.ref = "lwjgl" }
|
||||||
|
lwjgl-core-natives = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" }
|
||||||
|
lwjgl-glfw-natives = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl" }
|
||||||
|
lwjgl-opengl-natives = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" }
|
||||||
|
lwjgl-tinyfd-natives = { module = "org.lwjgl:lwjgl-tinyfd", version.ref = "lwjgl" }
|
||||||
|
|
||||||
|
imgui = { module = "io.github.spair:imgui-java-binding", version.ref = "imgui" } # https://github.com/SpaiR/imgui-java
|
||||||
|
imgui-lwjgl = { module = "io.github.spair:imgui-java-lwjgl3", version.ref = "imgui" }
|
||||||
|
imgui-natives-linux = { module = "io.github.spair:imgui-java-natives-linux", version.ref = "imgui" }
|
||||||
|
imgui-natives-windows = { module = "io.github.spair:imgui-java-natives-windows", version.ref = "imgui" }
|
||||||
|
imgui-natives-macos = { module = "io.github.spair:imgui-java-natives-macos", version.ref = "imgui" }
|
||||||
|
|
||||||
|
javagi-glib = { module = "io.github.jwharm.javagi:glib", version.ref = "javagi" }
|
||||||
|
javagi-gtk = { module = "io.github.jwharm.javagi:gtk", version.ref = "javagi" }
|
||||||
|
javagi-adw = { module = "io.github.jwharm.javagi:adw", version.ref = "javagi" }
|
||||||
|
|
||||||
|
commons = { module = "io.gitlab.jfronny:commons", version.ref = "jf-commons" }
|
||||||
|
commons-http-client = { module = "io.gitlab.jfronny:commons-http-client", version.ref = "jf-commons" }
|
||||||
|
commons-http-server = { module = "io.gitlab.jfronny:commons-http-server", version.ref = "jf-commons" }
|
||||||
|
commons-flow = { module = "io.gitlab.jfronny:commons-flow", version.ref = "jf-commons" }
|
||||||
|
commons-flow-backend-unsafe = { module = "io.gitlab.jfronny:commons-flow-backend-unsafe", version.ref = "jf-commons" }
|
||||||
|
commons-io = { module = "io.gitlab.jfronny:commons-io", version.ref = "jf-commons" }
|
||||||
|
commons-logger = { module = "io.gitlab.jfronny:commons-logger", version.ref = "jf-commons" }
|
||||||
|
commons-serialize-json = { module = "io.gitlab.jfronny:commons-serialize-json", version.ref = "jf-commons" }
|
||||||
|
commons-serialize-generator-annotations = { module = "io.gitlab.jfronny:commons-serialize-generator-annotations", version.ref = "jf-commons" }
|
||||||
|
commons-serialize-generator = { module = "io.gitlab.jfronny:commons-serialize-generator", version.ref = "jf-commons" }
|
||||||
|
|
||||||
|
annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" }
|
||||||
|
|
||||||
|
[bundles]
|
||||||
|
lwjgl = ["lwjgl-core", "lwjgl-glfw", "lwjgl-opengl", "lwjgl-tinyfd"]
|
||||||
|
lwjgl-natives = ["lwjgl-core-natives", "lwjgl-glfw-natives", "lwjgl-opengl-natives", "lwjgl-tinyfd-natives"]
|
||||||
|
javagi = ["javagi-glib", "javagi-gtk", "javagi-adw"]
|
||||||
|
commons = ["commons", "commons-http-client", "commons-io", "commons-logger", "commons-serialize-json"]
|
@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("inceptum.application-conventions")
|
inceptum.application
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
@ -7,5 +7,5 @@ application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":launcher"))
|
implementation(projects.launcher)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package io.gitlab.jfronny.inceptum.cli;
|
package io.gitlab.jfronny.inceptum.cli;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.io.JFiles;
|
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||||
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
||||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.GC_InstanceMeta;
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -11,11 +13,7 @@ import java.nio.file.Path;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class BaseInstanceCommand extends Command {
|
public abstract class BaseInstanceCommand extends Command {
|
||||||
public BaseInstanceCommand(String help, String usage, String... aliases) {
|
protected BaseInstanceCommand(String help, String usage, List<String> aliases, List<Command> subCommands) {
|
||||||
super(help, mutateUsage(usage), aliases);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BaseInstanceCommand(String help, String usage, List<String> aliases, List<Command> subCommands) {
|
|
||||||
super(help, mutateUsage(usage), aliases, subCommands);
|
super(help, mutateUsage(usage), aliases, subCommands);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,31 +29,34 @@ public abstract class BaseInstanceCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args) {
|
protected void invoke(CommandArgs args) throws Exception {
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
Utils.LOGGER.error("You must specify an instance to commit in");
|
Utils.LOGGER.error("You must specify an instance to commit in");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Path instancePath = MetaHolder.INSTANCE_DIR.resolve(args.get(0));
|
Instance instance;
|
||||||
Path instanceMetaPath = instancePath.resolve("instance.json");
|
Path normalPath = Path.of(args.get(0));
|
||||||
if (!Files.exists(instanceMetaPath)) {
|
if (Files.exists(normalPath.resolve(Instance.CONFIG_NAME))) {
|
||||||
|
instance = new Instance(normalPath, GC_InstanceMeta.deserialize(normalPath.resolve(Instance.CONFIG_NAME), GsonPreset.CONFIG));
|
||||||
|
} else {
|
||||||
|
Path instancePath = MetaHolder.INSTANCE_DIR.resolve(args.get(0)).normalize();
|
||||||
|
if (!instancePath.startsWith(MetaHolder.INSTANCE_DIR)) {
|
||||||
|
Utils.LOGGER.error("Specified instance path doesn't exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Files.exists(instancePath)) {
|
||||||
Utils.LOGGER.error("Invalid instance: \"" + args.get(0) + "\"");
|
Utils.LOGGER.error("Invalid instance: \"" + args.get(0) + "\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
InstanceMeta meta;
|
|
||||||
try {
|
try {
|
||||||
meta = JFiles.readObject(instanceMetaPath, InstanceMeta.class);
|
instance = InstanceList.read(instancePath);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Utils.LOGGER.error("Could not read instance metadata", e);
|
Utils.LOGGER.error("Could not read instance metadata", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
invoke(args.subArgs(), instancePath, meta);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Utils.LOGGER.error("Could not execute command", e);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
invoke(args.subArgs(), instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws Exception;
|
protected abstract void invoke(CommandArgs args, Instance instance) throws Exception;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ public class CliMain {
|
|||||||
new LaunchCommand(),
|
new LaunchCommand(),
|
||||||
new ListCommand(),
|
new ListCommand(),
|
||||||
new ModCommand(),
|
new ModCommand(),
|
||||||
|
new ImportCommand(),
|
||||||
new ExportCommand(),
|
new ExportCommand(),
|
||||||
new JvmStateCommand(),
|
new JvmStateCommand(),
|
||||||
new BatchCommand()
|
new BatchCommand()
|
||||||
@ -46,6 +47,8 @@ public class CliMain {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
command.invoke();
|
command.invoke();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Utils.LOGGER.error("Could not execute command", e);
|
||||||
} finally {
|
} finally {
|
||||||
LauncherEnv.terminate();
|
LauncherEnv.terminate();
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ public class HelpBuilder {
|
|||||||
builder.append(", ");
|
builder.append(", ");
|
||||||
}
|
}
|
||||||
builder.append(": ").append(help.replace("\n", "\n " + indent));
|
builder.append(": ").append(help.replace("\n", "\n " + indent));
|
||||||
if (level == 0 && !usage.isBlank() && !aliases.isEmpty()) {
|
if (level == 0 && !usage.isBlank()) {
|
||||||
StringBuilder usagePrefix = new StringBuilder("inceptum");
|
StringBuilder usagePrefix = new StringBuilder("inceptum");
|
||||||
for (String s : upper) {
|
for (String s : upper) {
|
||||||
usagePrefix.append(" ").append(s);
|
usagePrefix.append(" ").append(s);
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package io.gitlab.jfronny.inceptum.cli.commands;
|
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||||
|
|
||||||
import io.gitlab.jfronny.inceptum.cli.*;
|
import io.gitlab.jfronny.inceptum.cli.*;
|
||||||
import io.gitlab.jfronny.inceptum.common.R;
|
import io.gitlab.jfronny.inceptum.launcher.system.exporter.Exporters;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.export.Exporters;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
|
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -25,14 +22,11 @@ public class ExportCommand extends BaseInstanceCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws Exception {
|
protected void invoke(CommandArgs args, Instance instance) throws Exception {
|
||||||
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
||||||
if (args.length == 1) throw new IllegalAccessException("You must specify a version number");
|
if (args.length > 2) throw new IllegalAccessException("Too many arguments");
|
||||||
if (args.length != 2) throw new IllegalAccessException("Too many arguments");
|
if (args.length > 1) instance.meta().instanceVersion = args.get(1);
|
||||||
ProcessState state = new ProcessState();
|
Exporters.CURSE_FORGE.generate(new ProcessState(), instance, Paths.get(args.get(0)));
|
||||||
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
|
|
||||||
mds.runOnce(R::nop);
|
|
||||||
Exporters.CURSE_FORGE.generate(state, instancePath, meta, mds, Paths.get(args.get(0)), args.get(1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MultiMCExportCommand extends BaseInstanceCommand {
|
private static class MultiMCExportCommand extends BaseInstanceCommand {
|
||||||
@ -41,13 +35,10 @@ public class ExportCommand extends BaseInstanceCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws Exception {
|
protected void invoke(CommandArgs args, Instance instance) throws Exception {
|
||||||
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
||||||
if (args.length != 1) throw new IllegalAccessException("Too many arguments");
|
if (args.length != 1) throw new IllegalAccessException("Too many arguments");
|
||||||
ProcessState state = new ProcessState();
|
Exporters.MULTI_MC.generate(new ProcessState(), instance, Paths.get(args.get(0)));
|
||||||
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
|
|
||||||
mds.runOnce(R::nop);
|
|
||||||
Exporters.MULTI_MC.generate(state, instancePath, meta, mds, Paths.get(args.get(0)), "1.0");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,14 +48,11 @@ public class ExportCommand extends BaseInstanceCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws Exception {
|
protected void invoke(CommandArgs args, Instance instance) throws Exception {
|
||||||
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
if (args.length == 0) throw new IllegalAccessException("You must specify a target path");
|
||||||
if (args.length == 1) throw new IllegalAccessException("You must specify a version number");
|
if (args.length > 2) throw new IllegalAccessException("Too many arguments");
|
||||||
if (args.length != 2) throw new IllegalAccessException("Too many arguments");
|
if (args.length > 1) instance.meta().instanceVersion = args.get(1);
|
||||||
ProcessState state = new ProcessState();
|
Exporters.MODRINTH.generate(new ProcessState(), instance, Paths.get(args.get(0)));
|
||||||
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
|
|
||||||
mds.runOnce(R::nop);
|
|
||||||
Exporters.MODRINTH.generate(state, instancePath, meta, mds, Paths.get(args.get(0)), args.get(1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.logger.OutputColors;
|
||||||
|
import io.gitlab.jfronny.inceptum.cli.Command;
|
||||||
|
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.importer.Importers;
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ImportCommand extends Command {
|
||||||
|
public ImportCommand() {
|
||||||
|
super("Import a CurseForge, Modrinth or MultiMC instance", "<pack file>", List.of("import"), List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void invoke(CommandArgs args) throws Exception {
|
||||||
|
if (args.length == 0) throw new IllegalAccessException("You must specify a pack file");
|
||||||
|
if (args.length != 1) throw new IllegalAccessException("Too many arguments");
|
||||||
|
ProcessState state = new ProcessState();
|
||||||
|
String name = Importers.importPack(Paths.get(args.get(0)), state).path().getFileName().toString();
|
||||||
|
System.out.println(OutputColors.GREEN_BOLD + "Imported as " + name + OutputColors.RESET);
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,6 @@ public class JvmStateCommand extends Command {
|
|||||||
else
|
else
|
||||||
System.out.println("\t(cannot display components as not a URLClassLoader)");
|
System.out.println("\t(cannot display components as not a URLClassLoader)");
|
||||||
|
|
||||||
if (loader.getParent() != null)
|
if (loader.getParent() != null) dumpClasspath(loader.getParent());
|
||||||
dumpClasspath(loader.getParent());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,16 @@ package io.gitlab.jfronny.inceptum.cli.commands;
|
|||||||
|
|
||||||
import io.gitlab.jfronny.inceptum.cli.*;
|
import io.gitlab.jfronny.inceptum.cli.*;
|
||||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.launch.InstanceLauncher;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.util.InstanceLock;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager;
|
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.install.Steps;
|
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.launch.*;
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.util.Arrays;
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class LaunchCommand extends BaseInstanceCommand {
|
public class LaunchCommand extends BaseInstanceCommand {
|
||||||
private final boolean server;
|
private final boolean server;
|
||||||
@ -19,7 +20,7 @@ public class LaunchCommand extends BaseInstanceCommand {
|
|||||||
public LaunchCommand() {
|
public LaunchCommand() {
|
||||||
super("Launches an instance of the game (client by default). Non-blocking (batch commands will continue if this is ran)",
|
super("Launches an instance of the game (client by default). Non-blocking (batch commands will continue if this is ran)",
|
||||||
"[game arguments...]",
|
"[game arguments...]",
|
||||||
List.of("run", "launch", "start"),
|
List.of("run", "instance.launch", "start"),
|
||||||
List.of(
|
List.of(
|
||||||
new LaunchCommand("Explicitly launch a client", "client", false, false),
|
new LaunchCommand("Explicitly launch a client", "client", false, false),
|
||||||
new LaunchCommand("Launch a server", "server", true, false,
|
new LaunchCommand("Launch a server", "server", true, false,
|
||||||
@ -37,29 +38,28 @@ public class LaunchCommand extends BaseInstanceCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException, InstanceLauncher.LaunchException {
|
protected void invoke(CommandArgs args, Instance instance) throws IOException, LaunchException {
|
||||||
if (InstanceLock.isSetupLocked(instancePath)) {
|
if (instance.isSetupLocked()) {
|
||||||
Utils.LOGGER.error("This instance is still being set up");
|
Utils.LOGGER.error("This instance is still being set up");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (InstanceLock.isRunningLocked(instancePath)) {
|
if (instance.isRunningLocked()) {
|
||||||
Utils.LOGGER.error("This instance is already running");
|
Utils.LOGGER.error("This instance is already running");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (args.length > 1) {
|
if (args.length > 1) {
|
||||||
if (meta.arguments == null) meta.arguments = new InstanceMeta.Arguments();
|
InstanceMeta meta = instance.meta();
|
||||||
meta.arguments.client = meta.arguments.client == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.client);
|
meta.checkArguments();
|
||||||
meta.arguments.server = meta.arguments.server == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.server);
|
meta.arguments = meta.arguments
|
||||||
meta.arguments.jvm = meta.arguments.jvm == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.jvm);
|
.withClient(Stream.concat(meta.arguments.client().stream(), args.after(0).stream()).toList())
|
||||||
meta.arguments.client.addAll(args.after(0));
|
.withServer(Stream.concat(meta.arguments.server().stream(), args.after(0).stream()).toList());
|
||||||
meta.arguments.server.addAll(args.after(0));
|
|
||||||
}
|
}
|
||||||
Steps.reDownload(instancePath, Steps.createProcessState());
|
Steps.reDownload(instance, Steps.createProcessState());
|
||||||
if (server) {
|
if (server) {
|
||||||
InstanceLauncher.launch(instancePath, meta, InstanceLauncher.LaunchType.Server, restart, AccountManager.NULL_AUTH);
|
InstanceLauncher.launch(instance, LaunchType.Server, restart, AccountManager.NULL_AUTH);
|
||||||
} else {
|
} else {
|
||||||
AccountManager.loadAccounts();
|
AccountManager.loadAccounts();
|
||||||
InstanceLauncher.launchClient(instancePath, meta);
|
InstanceLauncher.launchClient(instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package io.gitlab.jfronny.inceptum.cli.commands;
|
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.io.JFiles;
|
import io.gitlab.jfronny.commons.io.JFiles;
|
||||||
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
|
||||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
|
||||||
import io.gitlab.jfronny.inceptum.cli.Command;
|
import io.gitlab.jfronny.inceptum.cli.Command;
|
||||||
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
|
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.util.InstanceLock;
|
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -23,29 +23,29 @@ public class ListCommand extends Command {
|
|||||||
List<Path> paths = JFiles.list(MetaHolder.INSTANCE_DIR);
|
List<Path> paths = JFiles.list(MetaHolder.INSTANCE_DIR);
|
||||||
if (paths.isEmpty()) System.out.println("No instances are currently present");
|
if (paths.isEmpty()) System.out.println("No instances are currently present");
|
||||||
for (Path path : paths) {
|
for (Path path : paths) {
|
||||||
if (!Files.exists(path.resolve("instance.json"))) {
|
if (!Files.exists(path.resolve(Instance.CONFIG_NAME))) {
|
||||||
System.out.println("- Invalid instance: " + path + " (no instance metadata)");
|
System.out.println("- Invalid instance: " + path + " (no instance metadata)");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
System.out.println("- \"" + path.getFileName().toString() + "\"");
|
System.out.println("- \"" + path.getFileName().toString() + "\"");
|
||||||
if (InstanceLock.isSetupLocked(path)) {
|
Instance instance;
|
||||||
System.out.println(" Status: Setting up");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
InstanceMeta instance;
|
|
||||||
try {
|
try {
|
||||||
instance = JFiles.readObject(path.resolve("instance.json"), InstanceMeta.class);
|
instance = InstanceList.read(path);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Utils.LOGGER.error(" Could not load instance.json", e);
|
Utils.LOGGER.error(" Could not load instance.json", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
System.out.println(" Status: " + (InstanceLock.isRunningLocked(path) ? "Running" : "Stopped"));
|
if (instance.isSetupLocked()) {
|
||||||
System.out.println(" Version: " + instance.getMinecraftVersion());
|
System.out.println(" Status: Setting up");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
System.out.println(" Status: " + (instance.isRunningLocked() ? "Running" : "Stopped"));
|
||||||
|
System.out.println(" Version: " + instance.getGameVersion());
|
||||||
if (instance.isFabric()) System.out.println(" Fabric Loader: " + instance.getLoaderVersion());
|
if (instance.isFabric()) System.out.println(" Fabric Loader: " + instance.getLoaderVersion());
|
||||||
if (instance.java != null) System.out.println(" Custom Java: " + instance.java);
|
if (instance.meta().java != null) System.out.println(" Custom Java: " + instance.meta().java);
|
||||||
if (instance.minMem != null || instance.maxMem != null)
|
if (instance.meta().minMem != null || instance.meta().maxMem != null)
|
||||||
System.out.println(" Memory:" + (instance.minMem != null ? " Minimum: " + instance.minMem : "")
|
System.out.println(" Memory:" + (instance.meta().minMem != null ? " Minimum: " + instance.meta().minMem : "")
|
||||||
+ (instance.maxMem != null ? " Maximum: " + instance.maxMem : ""));
|
+ (instance.meta().maxMem != null ? " Maximum: " + instance.meta().maxMem : ""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,9 @@ import io.gitlab.jfronny.commons.io.JFiles;
|
|||||||
import io.gitlab.jfronny.commons.throwable.ThrowingBiFunction;
|
import io.gitlab.jfronny.commons.throwable.ThrowingBiFunction;
|
||||||
import io.gitlab.jfronny.inceptum.cli.*;
|
import io.gitlab.jfronny.inceptum.cli.*;
|
||||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.util.ModManager;
|
import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.util.ModPath;
|
import io.gitlab.jfronny.inceptum.launcher.util.Unchecked;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.mds.IWModDescription;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -47,16 +44,15 @@ public class ModCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException {
|
protected void invoke(CommandArgs args, Instance instance) throws IOException {
|
||||||
if (!meta.isFabric()) {
|
if (!instance.isFabric()) {
|
||||||
System.err.println("This is not a fabric instance");
|
System.err.println("This is not a fabric instance");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System.out.println("Scanning installed mods, this might take a while");
|
System.out.println("Scanning installed mods, this might take a while");
|
||||||
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
|
instance.mds().runOnce(ScanStage.ALL, (path, mod) -> {
|
||||||
mds.runOnce((path, mod) -> {
|
boolean hasSources = !mod.getMetadata().sources().isEmpty();
|
||||||
boolean hasSources = mod.mod().isPresent() && !mod.mod().get().sources.isEmpty();
|
boolean updatable = hasSources && mod.getMetadata().sources().values().stream().anyMatch(Optional::isPresent);
|
||||||
boolean updatable = hasSources && mod.mod().get().sources.values().stream().anyMatch(Optional::isPresent);
|
|
||||||
if (filterUpdatable && !updatable) return;
|
if (filterUpdatable && !updatable) return;
|
||||||
System.out.println("- " + path.getFileName().toString());
|
System.out.println("- " + path.getFileName().toString());
|
||||||
System.out.println(" " + mod.getName());
|
System.out.println(" " + mod.getName());
|
||||||
@ -65,11 +61,10 @@ public class ModCommand extends Command {
|
|||||||
}
|
}
|
||||||
if (hasSources) {
|
if (hasSources) {
|
||||||
System.out.println(" Sources:");
|
System.out.println(" Sources:");
|
||||||
for (Map.Entry<ModSource, Optional<ModSource>> entry : mod.mod().get().sources.entrySet()) {
|
for (var entry : mod.getMetadata().sources().entrySet()) {
|
||||||
System.out.println(" - " + entry.getKey().getName() + " (" + entry.getKey().getVersion() + ")");
|
System.out.println(" - " + entry.getKey().getName() + " (" + entry.getKey().getVersion() + ")");
|
||||||
System.out.println(" Local: " + entry.getKey().getJarPath().toString());
|
System.out.println(" Local: " + entry.getKey().getJarPath().toString());
|
||||||
if (entry.getValue().isPresent())
|
if (entry.getValue().isPresent()) System.out.println(" Updatable to: " + entry.getValue().get().getVersion());
|
||||||
System.out.println(" Updatable to: " + entry.getValue().get().getVersion());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -91,8 +86,8 @@ public class ModCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException {
|
protected void invoke(CommandArgs args, Instance instance) throws IOException {
|
||||||
if (!meta.isFabric()) {
|
if (!instance.isFabric()) {
|
||||||
Utils.LOGGER.error("This is not a fabric instance");
|
Utils.LOGGER.error("This is not a fabric instance");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -102,22 +97,22 @@ public class ModCommand extends Command {
|
|||||||
}
|
}
|
||||||
Set<Path> mods = new HashSet<>();
|
Set<Path> mods = new HashSet<>();
|
||||||
for (String arg : args) {
|
for (String arg : args) {
|
||||||
Path p = instancePath.resolve("mods").resolve(arg);
|
Path p = instance.getModsDir().resolve(arg);
|
||||||
if (!Files.exists(p)) p = instancePath.resolve("mods").resolve(arg + ".imod");
|
if (!Files.exists(p)) p = instance.getModsDir().resolve(arg + ".imod");
|
||||||
if (!Files.exists(p)) {
|
if (!Files.exists(p)) {
|
||||||
Utils.LOGGER.error("Nonexistant mod file: " + instancePath.resolve("mods").resolve(arg));
|
Utils.LOGGER.error("Nonexistant mod file: " + instance.getModsDir().resolve(arg));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mods.add(p);
|
mods.add(p);
|
||||||
}
|
}
|
||||||
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
|
ModsDirScanner mds = instance.mds();
|
||||||
if (!ignoreDependencies && !mds.isComplete()) {
|
if (!ignoreDependencies && !mds.isComplete(ScanStage.DISCOVER)) {
|
||||||
Utils.LOGGER.error("Scanning mods dir to search for dependencies. This might take a while");
|
Utils.LOGGER.error("Scanning mods dir to search for dependencies. This might take a while");
|
||||||
mds.runOnce((path, mod) -> System.out.println("Scanned " + path));
|
mds.runOnce(ScanStage.DISCOVER, (path, mod) -> System.out.println("Scanned " + path));
|
||||||
}
|
}
|
||||||
for (Path mod : mods) {
|
for (Path mod : mods) {
|
||||||
try {
|
try {
|
||||||
ModManager.delete(mds.get(mod), instancePath.resolve("mods"), mds);
|
mds.get(mod).delete();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Utils.LOGGER.error("Could not delete " + mod, e);
|
Utils.LOGGER.error("Could not delete " + mod, e);
|
||||||
return;
|
return;
|
||||||
@ -152,50 +147,40 @@ public class ModCommand extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException {
|
protected void invoke(CommandArgs args, Instance instance) throws IOException {
|
||||||
if (!meta.isFabric()) {
|
if (!instance.isFabric()) {
|
||||||
throw new IOException("This is not a fabric instance");
|
throw new IOException("This is not a fabric instance");
|
||||||
}
|
}
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
throw new IllegalArgumentException("You must specify mods to remove");
|
throw new IllegalArgumentException("You must specify mods to remove");
|
||||||
}
|
}
|
||||||
Set<Path> mods = pathSupplier.apply(args, instancePath);
|
Set<Path> mods = pathSupplier.apply(args, instance.path());
|
||||||
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
|
ModsDirScanner mds = instance.mds();
|
||||||
if (!mds.isComplete()) {
|
if (!mds.isComplete(ScanStage.UPDATECHECK)) {
|
||||||
Utils.LOGGER.error("Scanning mods dir to search for dependencies. This might take a while");
|
Utils.LOGGER.error("Scanning mods dir to search for dependencies. This might take a while");
|
||||||
mds.runOnce((path, mod) -> System.out.println("Scanned " + path));
|
mds.runOnce(ScanStage.UPDATECHECK, (path, mod) -> System.out.println("Scanned " + path));
|
||||||
}
|
}
|
||||||
for (Path mod : mods) {
|
for (Path mod : mods) {
|
||||||
try {
|
try {
|
||||||
Utils.LOGGER.info("Updating " + mod);
|
Mod md = mds.get(mod);
|
||||||
ModManager.delete(mds.get(mod), instancePath.resolve("mods"), mds);
|
md.delete();
|
||||||
IWModDescription md = mds.get(mod);
|
md.getMetadata().sources().values().stream()
|
||||||
if (md.mod().isEmpty()) {
|
.filter(Optional::isPresent)
|
||||||
throw new IOException("Could not load mod description");
|
.map(Optional::get)
|
||||||
}
|
.findFirst()
|
||||||
boolean found = false;
|
.ifPresentOrElse(update -> {
|
||||||
for (Map.Entry<ModSource, Optional<ModSource>> source : md.mod().get().sources.entrySet()) {
|
|
||||||
Optional<ModSource> ms = source.getValue();
|
|
||||||
if (ms.isPresent()) {
|
|
||||||
try {
|
try {
|
||||||
Utils.LOGGER.info("Updating to " + ms.get().getVersion());
|
Utils.LOGGER.info("Updating " + mod + " to " + update.getVersion());
|
||||||
Path imodPath = md.imod().isPresent() ? md.imod().get() : instancePath.resolve("mods").resolve(ms.get().getShortName() + ModPath.EXT_IMOD);
|
md.update(update);
|
||||||
ModManager.DownloadMeta dm = ModManager.download(ms.get(), imodPath, mds);
|
|
||||||
Files.delete(md.path());
|
|
||||||
if (md.imod().isPresent() && Files.exists(md.imod().get()))
|
|
||||||
Files.delete(md.imod().get());
|
|
||||||
dm.write();
|
|
||||||
mds.invalidate(imodPath);
|
|
||||||
Utils.LOGGER.info("Update completed");
|
Utils.LOGGER.info("Update completed");
|
||||||
found = true;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IOException("Update failed", e);
|
throw new Unchecked(e);
|
||||||
}
|
}
|
||||||
}
|
}, () -> Utils.LOGGER.error("Could not find any update for " + mod));
|
||||||
}
|
|
||||||
if (!found) Utils.LOGGER.error("Could not find any update");
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IOException("Could not delete " + mod, e);
|
throw new IOException("Could not delete " + mod, e);
|
||||||
|
} catch (Unchecked e) {
|
||||||
|
throw new IOException("Could not delete " + mod, e.exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
launcher-cli/src/main/java/module-info.java
Normal file
6
launcher-cli/src/main/java/module-info.java
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module io.gitlab.jfronny.inceptum.launcher.cli {
|
||||||
|
exports io.gitlab.jfronny.inceptum.cli;
|
||||||
|
|
||||||
|
requires transitive io.gitlab.jfronny.inceptum.launcher;
|
||||||
|
requires static org.jetbrains.annotations;
|
||||||
|
}
|
@ -1,15 +1,22 @@
|
|||||||
|
import io.gitlab.jfronny.scripts.*
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("inceptum.application-standalone-conventions")
|
inceptum.`application-standalone`
|
||||||
|
org.beryx.jlink
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set("io.gitlab.jfronny.inceptum.Inceptum")
|
mainClass.set("io.gitlab.jfronny.inceptum.Inceptum")
|
||||||
|
mainModule.set("io.gitlab.jfronny.inceptum.launcher.dist")
|
||||||
|
applicationName = "Inceptum"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":launcher"))
|
implementation(projects.launcher)
|
||||||
implementation(project(":launcher-cli"))
|
implementation(projects.launcherCli)
|
||||||
implementation(project(":launcher-imgui"))
|
implementation(projects.launcherImgui)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.shadowJar {
|
tasks.shadowJar {
|
||||||
@ -17,7 +24,8 @@ tasks.shadowJar {
|
|||||||
archiveBaseName.set("Inceptum")
|
archiveBaseName.set("Inceptum")
|
||||||
exclude("about.html")
|
exclude("about.html")
|
||||||
exclude("plugin.properties")
|
exclude("plugin.properties")
|
||||||
exclude("META-INF/**")
|
// exclude("META-INF/**")
|
||||||
|
mergeServiceFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
(components["java"] as AdhocComponentWithVariants).withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
|
(components["java"] as AdhocComponentWithVariants).withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
|
||||||
@ -26,17 +34,115 @@ tasks.shadowJar {
|
|||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
if (rootProject.hasProperty("dist.platformOnly")) {
|
|
||||||
create<MavenPublication>("shadowed") {
|
|
||||||
artifact(tasks.shadowJar) {
|
|
||||||
builtBy(tasks.shadowJar)
|
|
||||||
artifactId = "Inceptum"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
create<MavenPublication>("mavenJava") {
|
create<MavenPublication>("mavenJava") {
|
||||||
from(components["java"])
|
from(components["java"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val flavor: String by rootProject.extra
|
||||||
|
val os = org.gradle.internal.os.OperatingSystem.current()!!
|
||||||
|
val crosscompile = flavor == "windows" && os.isLinux && project.hasProperty("crosscompile")
|
||||||
|
|
||||||
|
val verifyFlavorConfiguration by tasks.registering {
|
||||||
|
when (flavor) {
|
||||||
|
"macos" -> if (!os.isMacOsX) throw IllegalArgumentException("Cross-compilation to MacOS is unsupported!")
|
||||||
|
"windows" -> if (!os.isWindows && !crosscompile) {
|
||||||
|
if (os.isLinux) throw IllegalArgumentException("Cross-compilation to Windows is not enabled, please do so to build this")
|
||||||
|
else throw IllegalArgumentException("Cross-compilation to Windows from this platform is unsupported!")
|
||||||
|
}
|
||||||
|
"linux" -> if (!os.isLinux) throw IllegalArgumentException("Cross-compilation to Linux is unsupported!")
|
||||||
|
else -> throw IllegalArgumentException("Unexpected flavor: $flavor")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.prepareMergedJarsDir { dependsOn(verifyFlavorConfiguration) }
|
||||||
|
tasks.createMergedModule { dependsOn(verifyFlavorConfiguration) }
|
||||||
|
tasks.createDelegatingModules { dependsOn(verifyFlavorConfiguration) }
|
||||||
|
tasks.prepareModulesDir { dependsOn(verifyFlavorConfiguration) }
|
||||||
|
tasks.jlink { dependsOn(verifyFlavorConfiguration) }
|
||||||
|
tasks.jlinkZip { dependsOn(verifyFlavorConfiguration) }
|
||||||
|
tasks.suggestMergedModuleInfo { dependsOn(verifyFlavorConfiguration) }
|
||||||
|
tasks.jpackageImage { dependsOn(verifyFlavorConfiguration) }
|
||||||
|
tasks.jpackage { dependsOn(verifyFlavorConfiguration) }
|
||||||
|
|
||||||
|
fun computeDebugVersion(): String {
|
||||||
|
val time = System.currentTimeMillis() / 1000 / 60 / 5 // 5 minute intervals
|
||||||
|
// installer versions MUST be [0-255].[0-255].[0-65535]
|
||||||
|
// in other words, 8,8,16 bits
|
||||||
|
val major = (time / (256 * 65536))
|
||||||
|
val minor = (time / 65536) % 256
|
||||||
|
val patch = time % 65536
|
||||||
|
return "$major.$minor.$patch"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crosscompile) System.setProperty("badass.jlink.jpackage.home", "/root/jpackage-win")
|
||||||
|
jlink {
|
||||||
|
if (crosscompile) javaHome.set("/root/jpackage-win")
|
||||||
|
addOptions(
|
||||||
|
"--strip-debug",
|
||||||
|
"--compress", "2",
|
||||||
|
"--no-header-files",
|
||||||
|
"--no-man-pages",
|
||||||
|
"--verbose"
|
||||||
|
)
|
||||||
|
launcher {
|
||||||
|
name = application.applicationName
|
||||||
|
}
|
||||||
|
if (crosscompile) targetPlatform("win", "/root/java")
|
||||||
|
jpackage {
|
||||||
|
imageName = application.applicationName
|
||||||
|
if (crosscompile) {
|
||||||
|
outputDir = "/root/jpackage-out"
|
||||||
|
imageOutputDir = File(outputDir)
|
||||||
|
installerOutputDir = File(outputDir)
|
||||||
|
}
|
||||||
|
appVersion = versionStripped
|
||||||
|
// vendor
|
||||||
|
when (flavor) {
|
||||||
|
"macos" -> {
|
||||||
|
installerType = "app-image"
|
||||||
|
installerOptions.addAll(listOf(
|
||||||
|
"--mac-package-name", "inceptum"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
"windows" -> {
|
||||||
|
val release = project.hasProperty("release")
|
||||||
|
installerType = "msi"
|
||||||
|
installerOptions.addAll(listOf(
|
||||||
|
"--win-per-user-install",
|
||||||
|
"--win-dir-chooser",
|
||||||
|
"--win-menu",
|
||||||
|
"--win-upgrade-uuid", if (release) "3cda7403-4c00-4f9f-bcc3-6ff304566633" else "180becd8-a867-40d4-86ef-20949cae68b5" // Update these UUIDs if you fork the project!!!
|
||||||
|
))
|
||||||
|
if (!release) appVersion = computeDebugVersion()
|
||||||
|
//imageOptions.add("--win-console") // Enable this for debugging
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// might also be able to push rpm with appropriate image
|
||||||
|
installerType = "deb"
|
||||||
|
installerOptions.addAll(listOf(
|
||||||
|
"--linux-package-name", "inceptum",
|
||||||
|
"--linux-shortcut"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crosscompile) {
|
||||||
|
tasks.jpackage {
|
||||||
|
doLast {
|
||||||
|
val src = Path.of("/root/jpackage-out")
|
||||||
|
val trg = layout.buildDirectory.dir("jpackage").get().asFile.toPath()
|
||||||
|
Files.createDirectories(trg)
|
||||||
|
Files.list(src).use {
|
||||||
|
it.filter { Files.isRegularFile(it) }.forEach {
|
||||||
|
val t = trg.resolve(it.fileName.toString())
|
||||||
|
println("Moving $it to $t")
|
||||||
|
Files.move(it, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -14,7 +14,7 @@ public class GuiCommand extends Command {
|
|||||||
@Override
|
@Override
|
||||||
public void invoke(CommandArgs args) {
|
public void invoke(CommandArgs args) {
|
||||||
LauncherEnv.updateBackend(new GuiEnvBackend());
|
LauncherEnv.updateBackend(new GuiEnvBackend());
|
||||||
GuiMain.main();
|
GuiMain.showGui();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -30,19 +30,25 @@ public class UpdateCheckCommand extends Command {
|
|||||||
Utils.LOGGER.error("Automatic updates are not supported without the wrapper");
|
Utils.LOGGER.error("Automatic updates are not supported without the wrapper");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UpdateMetadata updateUrl = BuildMetadata.IS_PUBLIC ? Updater.getUpdate() : null;
|
UpdateMetadata update;
|
||||||
if (updateUrl == null) {
|
try {
|
||||||
|
update = BuildMetadata.IS_PUBLIC ? Updater.getUpdate(true, true) : null;
|
||||||
|
} catch (Updater.UpdateCheckException e) {
|
||||||
|
Utils.LOGGER.error("Latest update is not compatible: " + e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (update == null) {
|
||||||
Utils.LOGGER.info("No update was found");
|
Utils.LOGGER.info("No update was found");
|
||||||
} else {
|
} else {
|
||||||
if (install) {
|
if (install) {
|
||||||
Utils.LOGGER.info("Installing from " + updateUrl);
|
Utils.LOGGER.info("Installing from " + update);
|
||||||
try {
|
try {
|
||||||
Updater.update(updateUrl, false);
|
Updater.update(update, false);
|
||||||
} catch (IOException | URISyntaxException e) {
|
} catch (IOException | URISyntaxException e) {
|
||||||
Utils.LOGGER.error("Could not download update", e);
|
Utils.LOGGER.error("Could not download update", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Utils.LOGGER.info("An update was found: " + updateUrl);
|
Utils.LOGGER.info("An update was found: " + update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
launcher-dist/src/main/java/module-info.java
Normal file
4
launcher-dist/src/main/java/module-info.java
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module io.gitlab.jfronny.inceptum.launcher.dist {
|
||||||
|
requires io.gitlab.jfronny.inceptum.launcher.imgui;
|
||||||
|
requires io.gitlab.jfronny.inceptum.launcher.cli;
|
||||||
|
}
|
43
launcher-gtk/build.gradle.kts
Normal file
43
launcher-gtk/build.gradle.kts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
plugins {
|
||||||
|
inceptum.application
|
||||||
|
com.github.johnrengelman.shadow
|
||||||
|
kotlin("jvm") version libs.versions.kotlin
|
||||||
|
kotlin("plugin.sam.with.receiver") version libs.versions.kotlin
|
||||||
|
}
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainClass.set("io.gitlab.jfronny.inceptum.gtk.GtkMain")
|
||||||
|
}
|
||||||
|
|
||||||
|
samWithReceiver {
|
||||||
|
annotation("io.gitlab.jfronny.commons.SamWithReceiver")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.bundles.javagi)
|
||||||
|
implementation(projects.launcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.shadowJar {
|
||||||
|
mergeServiceFiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.runShadow {
|
||||||
|
if (project.hasProperty("showcase")) {
|
||||||
|
environment("GTK_THEME", "Adwaita")
|
||||||
|
environment("GDK_BACKEND", "broadway")
|
||||||
|
environment("BROADWAY_DISPLAY", ":5")
|
||||||
|
var proc: Process? = null
|
||||||
|
doFirst {
|
||||||
|
proc = Runtime.getRuntime().exec(arrayOf("gtk4-broadwayd", ":5"))
|
||||||
|
Runtime.getRuntime().exec(arrayOf("xdg-open", "http://127.0.0.1:8085"))
|
||||||
|
Thread.sleep(1000)
|
||||||
|
}
|
||||||
|
doLast {
|
||||||
|
if (proc != null) Runtime.getRuntime().exec(arrayOf("kill", proc!!.pid().toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
workingDir = rootProject.projectDir
|
||||||
|
environment("GTK_DEBUG", "interactive") // interactive:actions
|
||||||
|
jvmArgs("--enable-preview", "--enable-native-access=ALL-UNNAMED")
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.StringFormatter
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.dialog.MicrosoftLoginDialog
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.dialog.StringInputDialog
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv.EnvBackend
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount
|
||||||
|
import org.gnome.gio.Cancellable
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
object GtkEnvBackend : EnvBackend {
|
||||||
|
var dialogParent: Window? = null
|
||||||
|
|
||||||
|
override fun showError(message: String, title: String) {
|
||||||
|
Log.error(message)
|
||||||
|
simpleDialog(message, title, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showError(message: String, t: Throwable) {
|
||||||
|
simpleDialog(StringFormatter.toString(t), message, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showInfo(message: String, title: String) {
|
||||||
|
Log.info(message)
|
||||||
|
simpleDialog(message, title, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showOkCancel(message: String, title: String, ok: Runnable, cancel: Runnable, defaultCancel: Boolean) {
|
||||||
|
Log.info(message)
|
||||||
|
simpleDialog(message, title, ok, cancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getInput(
|
||||||
|
prompt: String,
|
||||||
|
details: String,
|
||||||
|
defaultValue: String,
|
||||||
|
ok: Consumer<String>,
|
||||||
|
cancel: Runnable
|
||||||
|
) = schedule {
|
||||||
|
val dialog = StringInputDialog(
|
||||||
|
dialogParent,
|
||||||
|
if (dialogParent == null) setOf(DialogFlags.DESTROY_WITH_PARENT) else setOf(DialogFlags.DESTROY_WITH_PARENT, DialogFlags.MODAL),
|
||||||
|
MessageType.QUESTION,
|
||||||
|
ButtonsType.OK_CANCEL,
|
||||||
|
details,
|
||||||
|
defaultValue
|
||||||
|
)
|
||||||
|
dialog.title = prompt
|
||||||
|
dialog.onResponse(processResponses(dialog, { ok.accept(dialog.input) }, cancel))
|
||||||
|
dialog.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showLoginRefreshPrompt(account: MicrosoftAccount) =
|
||||||
|
schedule { MicrosoftLoginDialog(dialogParent, account).visible = true }
|
||||||
|
|
||||||
|
private fun simpleDialog(
|
||||||
|
markup: String,
|
||||||
|
title: String,
|
||||||
|
ok: Runnable?,
|
||||||
|
cancel: Runnable?
|
||||||
|
) = schedule { simpleDialog(dialogParent, markup, title, ok, cancel) }
|
||||||
|
|
||||||
|
fun simpleDialog(
|
||||||
|
parent: Window?,
|
||||||
|
markup: String,
|
||||||
|
title: String,
|
||||||
|
ok: Runnable?,
|
||||||
|
cancel: Runnable?
|
||||||
|
) {
|
||||||
|
val dialog = AlertDialog.builder()
|
||||||
|
.setMessage(title)
|
||||||
|
.setDetail(markup)
|
||||||
|
.setModal(true)
|
||||||
|
when {
|
||||||
|
cancel == null -> dialog.setButtons(arrayOf(I18n["ok"]))
|
||||||
|
.setDefaultButton(0)
|
||||||
|
.setCancelButton(-1)
|
||||||
|
ok == null -> dialog.setButtons(arrayOf("Cancel"))
|
||||||
|
.setDefaultButton(-1)
|
||||||
|
.setCancelButton(0)
|
||||||
|
else -> dialog.setButtons(arrayOf("OK", "Cancel"))
|
||||||
|
.setDefaultButton(0)
|
||||||
|
.setCancelButton(1)
|
||||||
|
}
|
||||||
|
dialog.build().apply {
|
||||||
|
choose(parent, Cancellable()) { _, res, _ ->
|
||||||
|
val result = chooseFinish(res)
|
||||||
|
val cancelIdx = cancelButton
|
||||||
|
val defaultIdx = defaultButton
|
||||||
|
if (result == cancelIdx) cancel?.run()
|
||||||
|
if (result == defaultIdx) ok?.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processResponses(dialog: Dialog, ok: Runnable?, cancel: Runnable?): Dialog.ResponseCallback = Dialog.ResponseCallback { responseId: Int ->
|
||||||
|
when (ResponseType.of(responseId)) {
|
||||||
|
ResponseType.OK -> {
|
||||||
|
dialog.close()
|
||||||
|
ok?.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseType.CLOSE, ResponseType.CANCEL -> {
|
||||||
|
dialog.close()
|
||||||
|
cancel?.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseType.DELETE_EVENT -> dialog.destroy()
|
||||||
|
else -> Log.error("Unexpected response type: $responseId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.common.BuildMetadata
|
||||||
|
import io.gitlab.jfronny.inceptum.common.MetaHolder
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.GtkLog
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.MainWindow
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList
|
||||||
|
import org.gnome.gio.ApplicationFlags
|
||||||
|
import org.gnome.glib.GLib
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
object GtkMain {
|
||||||
|
const val ID = "io.gitlab.jfronny.inceptum"
|
||||||
|
@Throws(IOException::class)
|
||||||
|
@JvmStatic
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
GLib.logSetWriterFunc(GtkLog)
|
||||||
|
LauncherEnv.initialize(GtkEnvBackend)
|
||||||
|
Log.info("Launching Inceptum v" + BuildMetadata.VERSION)
|
||||||
|
Log.info("Loading from " + MetaHolder.BASE_PATH)
|
||||||
|
exitProcess(try {
|
||||||
|
showGui(args)
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
-1
|
||||||
|
} finally {
|
||||||
|
LauncherEnv.terminate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showGui(args: Array<String>): Int = setupApplication(args) {
|
||||||
|
//TODO update check
|
||||||
|
AccountManager.loadAccounts()
|
||||||
|
try {
|
||||||
|
InstanceList.forEach<IOException> { it.mds.start() }
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error("Could not initialize MDS", e)
|
||||||
|
}
|
||||||
|
GtkMenubar.create(this)
|
||||||
|
val window = MainWindow(this)
|
||||||
|
window.visible = true
|
||||||
|
GtkEnvBackend.dialogParent = window
|
||||||
|
window.onCloseRequest {
|
||||||
|
GtkEnvBackend.dialogParent = null
|
||||||
|
this.quit()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setupApplication(args: Array<String>, onActivate: Application.() -> Unit): Int {
|
||||||
|
val app = Application(ID, ApplicationFlags.FLAGS_NONE)
|
||||||
|
app.onActivate {
|
||||||
|
GLib.idleAdd(GLib.PRIORITY_DEFAULT_IDLE) {
|
||||||
|
runScheduledTasks()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
onActivate(app)
|
||||||
|
}
|
||||||
|
return app.run(args)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,215 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.OSUtils
|
||||||
|
import io.gitlab.jfronny.commons.io.JFiles
|
||||||
|
import io.gitlab.jfronny.inceptum.common.MetaHolder
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.AboutWindow
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.create.NewInstanceWindow
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.dialog.MicrosoftLoginDialog
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.settings.launcher.LauncherSettingsWindow
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.importer.Importers
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.launch.InstanceLauncher
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState
|
||||||
|
import org.gnome.gio.Cancellable
|
||||||
|
import org.gnome.gio.Menu
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import java.awt.Toolkit
|
||||||
|
import java.awt.datatransfer.DataFlavor
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
object GtkMenubar {
|
||||||
|
var newMenu: MenuBuilder? = null
|
||||||
|
var accountsMenu: MenuBuilder? = null
|
||||||
|
var launchMenu: MenuBuilder? = null
|
||||||
|
|
||||||
|
fun create(app: Application) {
|
||||||
|
val menu = MenuBuilder(app, Menu()) // this should be MenuBuilder(app), but that breaks this
|
||||||
|
val file = menu.submenu("file")
|
||||||
|
newMenu = file.submenu("new")
|
||||||
|
generateNewMenu(app)
|
||||||
|
file.button("redownload") {
|
||||||
|
val state = ProcessState(3 + Steps.STEPS.size * InstanceList.size(), "Initializing")
|
||||||
|
ProcessStateWatcherDialog.show(
|
||||||
|
GtkEnvBackend.dialogParent,
|
||||||
|
"Reloading data",
|
||||||
|
"Could not execute refresh task",
|
||||||
|
state
|
||||||
|
) {
|
||||||
|
state.incrementStep("Clearing cache directories")
|
||||||
|
JFiles.clearDirectory(MetaHolder.ASSETS_DIR)
|
||||||
|
JFiles.clearDirectory(MetaHolder.LIBRARIES_DIR) { path: Path -> !path.startsWith(MetaHolder.LIBRARIES_DIR.resolve("io/gitlab/jfronny")) }
|
||||||
|
JFiles.clearDirectory(MetaHolder.NATIVES_DIR) { path: Path -> !path.startsWith(MetaHolder.NATIVES_DIR.resolve("forceload")) }
|
||||||
|
JFiles.clearDirectory(MetaHolder.CACHE_DIR)
|
||||||
|
if (state.isCancelled) return@show
|
||||||
|
state.incrementStep("Reloading instance list")
|
||||||
|
InstanceList.reset()
|
||||||
|
InstanceList.forEach<IOException> { instance: Instance? ->
|
||||||
|
if (state.isCancelled) return@forEach
|
||||||
|
Steps.reDownload(instance, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.button("exit") { app.quit() }
|
||||||
|
launchMenu = menu.submenu("launch")
|
||||||
|
generateLaunchMenu(app)
|
||||||
|
accountsMenu = menu.submenu("account")
|
||||||
|
generateAccountsMenu(app)
|
||||||
|
val help = menu.submenu("help")
|
||||||
|
help.button("about") { AboutWindow.createAndShow() }
|
||||||
|
help.button("log") {
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateNewMenu(app: Application) {
|
||||||
|
newMenu!!.clear()
|
||||||
|
newMenu!!.button("new") { NewInstanceWindow(app).visible = true }
|
||||||
|
newMenu!!.button("file") {
|
||||||
|
val dialog = FileChooserNative(
|
||||||
|
I18n["menu.file.new.file"],
|
||||||
|
GtkEnvBackend.dialogParent,
|
||||||
|
FileChooserAction.OPEN,
|
||||||
|
"_" + I18n["select"],
|
||||||
|
"_" + I18n["cancel"]
|
||||||
|
)
|
||||||
|
val filter = FileFilter()
|
||||||
|
filter.addPattern("*.zip")
|
||||||
|
filter.addPattern("*.mrpack")
|
||||||
|
dialog.addFilter(filter)
|
||||||
|
dialog.onResponse { responseId: Int ->
|
||||||
|
if (responseId == ResponseType.ACCEPT.value) {
|
||||||
|
val file = dialog.file!!.path
|
||||||
|
if (file == null) {
|
||||||
|
LauncherEnv.showError("The path returned by the file dialog is null", "Could not import")
|
||||||
|
return@onResponse
|
||||||
|
}
|
||||||
|
val state = ProcessState(Importers.MAX_STEPS, "Initializing")
|
||||||
|
ProcessStateWatcherDialog.show(
|
||||||
|
GtkEnvBackend.dialogParent,
|
||||||
|
I18n["menu.file.new.file"],
|
||||||
|
I18n["menu.file.new.file.error"],
|
||||||
|
state
|
||||||
|
) {
|
||||||
|
Importers.importPack(Path.of(file), state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
newMenu!!.button("url") {
|
||||||
|
readClipboard { clipboard ->
|
||||||
|
LauncherEnv.getInput(
|
||||||
|
I18n["menu.file.new.url"],
|
||||||
|
I18n["menu.file.new.url.details"],
|
||||||
|
clipboard ?: "",
|
||||||
|
{ s: String? ->
|
||||||
|
val state = ProcessState(Importers.MAX_STEPS, "Initializing")
|
||||||
|
ProcessStateWatcherDialog.show(
|
||||||
|
GtkEnvBackend.dialogParent,
|
||||||
|
I18n["menu.file.new.url"],
|
||||||
|
I18n["menu.file.new.url.error"],
|
||||||
|
state
|
||||||
|
) {
|
||||||
|
Importers.importPack(s, state)
|
||||||
|
}
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readClipboard(continuation: (String?) -> Unit) {
|
||||||
|
if (OSUtils.TYPE == OSUtils.Type.LINUX) {
|
||||||
|
val clipboard = GtkEnvBackend.dialogParent!!.display.clipboard
|
||||||
|
clipboard.readTextAsync(Cancellable()) { _, res, _ ->
|
||||||
|
continuation(clipboard.readTextFinish(res))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continuation(Toolkit.getDefaultToolkit().systemClipboard.getData(DataFlavor.stringFlavor) as String?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateLaunchMenu(app: Application) {
|
||||||
|
launchMenu!!.clear()
|
||||||
|
try {
|
||||||
|
InstanceList.forEach<RuntimeException> { entry: Instance ->
|
||||||
|
launchMenu!!.literalButton(entry.id + ".launch", entry.toString()) {
|
||||||
|
launch(entry, LaunchType.Client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error("Could not generate launch menu", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun launch(instance: Instance, launchType: LaunchType) {
|
||||||
|
if (instance.isSetupLocked) {
|
||||||
|
LauncherEnv.showError(I18n["instance.launch.locked.setup"], I18n["instance.launch.locked"])
|
||||||
|
} else if (instance.isRunningLocked) {
|
||||||
|
LauncherEnv.showOkCancel(
|
||||||
|
I18n["instance.launch.locked.running"],
|
||||||
|
I18n["instance.launch.locked"]
|
||||||
|
) { forceLaunch(instance, launchType) }
|
||||||
|
} else forceLaunch(instance, launchType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun forceLaunch(instance: Instance, launchType: LaunchType) {
|
||||||
|
val state = Steps.createProcessState()
|
||||||
|
ProcessStateWatcherDialog.show(
|
||||||
|
GtkEnvBackend.dialogParent,
|
||||||
|
I18n["instance.launch.title"],
|
||||||
|
I18n["instance.launch.error"],
|
||||||
|
state
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
Steps.reDownload(instance, state)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error("Could not fetch instance, trying to start anyways", e)
|
||||||
|
}
|
||||||
|
if (state.isCancelled) return@show
|
||||||
|
state.updateStep("Starting Game")
|
||||||
|
try {
|
||||||
|
if (launchType == LaunchType.Client) InstanceLauncher.launchClient(instance)
|
||||||
|
else InstanceLauncher.launch(
|
||||||
|
instance,
|
||||||
|
launchType,
|
||||||
|
false,
|
||||||
|
AccountManager.NULL_AUTH
|
||||||
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
LauncherEnv.showError("Could not start instance", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateAccountsMenu(app: Application) {
|
||||||
|
accountsMenu!!.clear()
|
||||||
|
accountsMenu!!.button("new") { MicrosoftLoginDialog(GtkEnvBackend.dialogParent).visible = true }
|
||||||
|
accountsMenu!!.button("manage") {
|
||||||
|
val window = LauncherSettingsWindow(app)
|
||||||
|
window.activePage = "settings.accounts"
|
||||||
|
window.visible = true
|
||||||
|
}
|
||||||
|
val accounts: MutableList<MicrosoftAccount?> = ArrayList(AccountManager.getAccounts())
|
||||||
|
accounts.add(null)
|
||||||
|
accountsMenu!!.literalRadio(
|
||||||
|
"account",
|
||||||
|
accounts[AccountManager.getSelectedIndex()],
|
||||||
|
accounts,
|
||||||
|
{ _, acc: MicrosoftAccount? ->
|
||||||
|
if (acc == null) return@literalRadio I18n["account.none"]
|
||||||
|
acc.minecraftUsername
|
||||||
|
}) { account: MicrosoftAccount? -> AccountManager.switchAccount(account) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
private val SCHEDULED: Queue<Runnable> = ArrayDeque()
|
||||||
|
|
||||||
|
fun schedule(task: Runnable) {
|
||||||
|
SCHEDULED.add(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun backgroundTask(task: Runnable) {
|
||||||
|
Thread.ofVirtual().start(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun runScheduledTasks() {
|
||||||
|
var r: Runnable?
|
||||||
|
while (SCHEDULED.poll().also { r = it } != null) {
|
||||||
|
try {
|
||||||
|
r!!.run()
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
Log.error("Could not run scheduled task", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.GtkMain
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import org.gnome.gtk.CssProvider
|
||||||
|
import org.gnome.gtk.Gtk
|
||||||
|
import org.gnome.gtk.Label
|
||||||
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
|
||||||
|
class ILabel(
|
||||||
|
str: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
|
||||||
|
mode: Mode,
|
||||||
|
vararg args: Any?
|
||||||
|
) : Label(I18n.get(str, *args)) {
|
||||||
|
constructor(str: @PropertyKey(resourceBundle = I18n.BUNDLE) String, vararg args: Any?) : this(str, Mode.NORMAL, *args)
|
||||||
|
|
||||||
|
init {
|
||||||
|
theme(this, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
NORMAL,
|
||||||
|
HEADING,
|
||||||
|
SUBTITLE
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val provider by lazy {
|
||||||
|
val provider = CssProvider()
|
||||||
|
try {
|
||||||
|
GtkMain::class.java.classLoader.getResourceAsStream("inceptum.css")!!
|
||||||
|
.use {
|
||||||
|
val bytes = it.readAllBytes()
|
||||||
|
provider.loadFromData(String(bytes), bytes.size.toLong())
|
||||||
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
throw RuntimeException(t)
|
||||||
|
}
|
||||||
|
provider
|
||||||
|
}
|
||||||
|
|
||||||
|
fun theme(label: Label, mode: Mode) = when (mode) {
|
||||||
|
Mode.HEADING -> label.addCssClass("heading")
|
||||||
|
Mode.SUBTITLE -> {
|
||||||
|
label.addCssClass("jf-subtitle")
|
||||||
|
label.styleContext.addProvider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
Mode.NORMAL -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.get
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import org.gnome.pango.EllipsizeMode
|
||||||
|
import org.gnome.pango.WrapMode
|
||||||
|
|
||||||
|
class InstanceGridEntryFactory(
|
||||||
|
private val instanceList: List<Instance>
|
||||||
|
) : KSignalListItemFactory<InstanceGridEntryFactory.Decomposed, Box>() {
|
||||||
|
//TODO better design
|
||||||
|
override fun setup(): Box {
|
||||||
|
val box = Box(Orientation.VERTICAL, 5)
|
||||||
|
|
||||||
|
val thumbnail = InstanceThumbnail()
|
||||||
|
box.append(thumbnail)
|
||||||
|
|
||||||
|
val label = Label(null as String?)
|
||||||
|
label.setSizeRequest(192, -1)
|
||||||
|
label.maxWidthChars = 20
|
||||||
|
label.justify = Justification.CENTER
|
||||||
|
label.halign = Align.START
|
||||||
|
label.hexpand = true
|
||||||
|
label.valign = Align.CENTER
|
||||||
|
label.ellipsize = EllipsizeMode.MIDDLE
|
||||||
|
label.lines = 3
|
||||||
|
label.wrap = true
|
||||||
|
label.wrapMode = WrapMode.WORD_CHAR
|
||||||
|
label.marginTop = 10
|
||||||
|
box.append(label)
|
||||||
|
// Label label = new Label(Str.NULL);
|
||||||
|
// label.setXalign(0);
|
||||||
|
// label.setWidthChars(20);
|
||||||
|
// label.setMarginEnd(10);
|
||||||
|
// box.append(label);
|
||||||
|
//
|
||||||
|
// Button launch = new Button();
|
||||||
|
// launch.setIconName(new Str("computer-symbolic"));
|
||||||
|
// launch.setTooltipText(I18n.str("instance.launch"));
|
||||||
|
// launch.setHasTooltip(GTK.TRUE);
|
||||||
|
// box.append(launch);
|
||||||
|
//
|
||||||
|
// Button openDir = new Button();
|
||||||
|
// openDir.setIconName(new Str("folder-symbolic"));
|
||||||
|
// openDir.setTooltipText(I18n.str("instance.directory"));
|
||||||
|
// openDir.setHasTooltip(GTK.TRUE);
|
||||||
|
// box.append(openDir);
|
||||||
|
//TODO server launch with network-server-symbolic
|
||||||
|
//TODO kill current instance
|
||||||
|
return box
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun BindContext.bind(widget: Box, data: Decomposed) {
|
||||||
|
// Label label = new Label(item.getChild().getFirstChild().cast());
|
||||||
|
// Button launch = new Button(label.getNextSibling().cast());
|
||||||
|
// Button openDir = new Button(launch.getNextSibling().cast());
|
||||||
|
// InstanceList.Entry instance = instanceList.get(ListIndex.toIndex(item));
|
||||||
|
// label.setText(new Str(instance.toString()));
|
||||||
|
// launch.onClicked(() -> GtkMenubar.launch(instance));
|
||||||
|
// openDir.onClicked(() -> Utils.openFile(instance.path().toFile()));
|
||||||
|
val thumbnail = InstanceThumbnail.castFrom(widget.firstChild as Stack)
|
||||||
|
val label = thumbnail.nextSibling as Label
|
||||||
|
|
||||||
|
val instance = instanceList[data.id]
|
||||||
|
thumbnail.bind(instance!!)
|
||||||
|
label.text = instance.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun UnbindContext.unbind(widget: Box, data: Decomposed) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ActionContext.castWidget(widget: Widget): Box = widget as Box
|
||||||
|
override fun ActionContext.lookup(id: String, widget: Box): Decomposed = Decomposed(id)
|
||||||
|
|
||||||
|
class Decomposed(val id: String)
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.io.JFiles
|
||||||
|
import io.gitlab.jfronny.commons.ref.R
|
||||||
|
import io.gitlab.jfronny.inceptum.common.MetaHolder
|
||||||
|
import io.gitlab.jfronny.inceptum.common.Utils
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.GtkMenubar
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.*
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.settings.instance.InstanceSettingsWindow
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceNameTool
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
|
||||||
|
import org.gnome.adw.ActionRow
|
||||||
|
import org.gnome.gdk.Gdk
|
||||||
|
import org.gnome.gio.Menu
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class InstanceListEntryFactory(
|
||||||
|
private val app: Application?,
|
||||||
|
private val instanceList: List<Instance>
|
||||||
|
) : KSignalListItemFactory<InstanceListEntryFactory.Decomposed, ActionRow>() {
|
||||||
|
override fun setup(): ActionRow {
|
||||||
|
val thumbnail = InstanceThumbnail()
|
||||||
|
thumbnail.name = "inceptum-thumbnail"
|
||||||
|
|
||||||
|
val launch = Button.fromIconName("media-playback-start-symbolic")
|
||||||
|
launch.addCssClass("flat")
|
||||||
|
launch.name = "inceptum-launch"
|
||||||
|
launch.tooltipText = I18n["instance.launch"]
|
||||||
|
|
||||||
|
val menu = MenuButton()
|
||||||
|
menu.addCssClass("flat")
|
||||||
|
menu.iconName = "view-more-symbolic"
|
||||||
|
menu.setPopover(PopoverMenu.fromModel(Menu()))
|
||||||
|
|
||||||
|
val row = ActionRow()
|
||||||
|
row.margin = 8
|
||||||
|
row.name = "inceptum-row"
|
||||||
|
row.removeCssClass("activatable") //TODO remove this workaround if a better way to support opening the menu is found
|
||||||
|
row.addPrefix(thumbnail)
|
||||||
|
row.addSuffix(launch)
|
||||||
|
row.addSuffix(menu)
|
||||||
|
row.fixSubtitle()
|
||||||
|
|
||||||
|
val rightClicked = GestureClick()
|
||||||
|
rightClicked.button = Gdk.BUTTON_SECONDARY
|
||||||
|
rightClicked.onPressed { nPress, _, _ -> if (nPress == 1) menu.emitActivate() }
|
||||||
|
row.addController(rightClicked)
|
||||||
|
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun BindContext.bind(widget: ActionRow, data: Decomposed) {
|
||||||
|
if (data.instance?.isLocked ?: true) {
|
||||||
|
data.item.activatable = false
|
||||||
|
widget.subtitle = if (data.instance?.isRunningLocked ?: false) I18n["instance.launch.locked.running"]
|
||||||
|
else I18n["instance.launch.locked.setup"]
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.title = data.instance.toString().escapedMarkup
|
||||||
|
|
||||||
|
data.thumbnail.bind(data.instance!!)
|
||||||
|
|
||||||
|
val menuBuilder = MenuBuilder(data.popoverMenu, data.instanceId)
|
||||||
|
val launchSection = menuBuilder.literalSection("launch", null)
|
||||||
|
val kill = launchSection.literalButton("kill", I18n["instance.kill"]) {
|
||||||
|
//TODO test
|
||||||
|
LauncherEnv.showOkCancel(I18n["instance.kill.prompt"], I18n["instance.kill.details"]) {
|
||||||
|
if (!data.instance.kill()) LauncherEnv.showError(I18n["instance.kill.fail"], I18n["failed"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kill.enabled = data.instance.isRunningLocked
|
||||||
|
|
||||||
|
launchSection.literalButton(
|
||||||
|
"launch.client", I18n["instance.launch.client"]
|
||||||
|
) { GtkMenubar.launch(data.instance, LaunchType.Client) }.iconName = "media-playback-start-symbolic"
|
||||||
|
launchSection.literalButton(
|
||||||
|
"launch.server", I18n["instance.launch.server"]
|
||||||
|
) { GtkMenubar.launch(data.instance, LaunchType.Server) }.iconName = "network-server-symbolic"
|
||||||
|
val settingsSection = menuBuilder.literalSection("settings", null)
|
||||||
|
settingsSection.literalButton("settings", I18n["instance.settings"]) {
|
||||||
|
//TODO keep track of properties windows and don't allow opening two
|
||||||
|
InstanceSettingsWindow(app, data.instance).visible = true
|
||||||
|
}.iconName = "document-edit-symbolic"
|
||||||
|
settingsSection.literalButton(
|
||||||
|
"directory", I18n["instance.directory"]
|
||||||
|
) { Utils.openFile(data.instance.path.toFile()) }.iconName = "folder-symbolic"
|
||||||
|
settingsSection.literalButton("copy", I18n["instance.copy"]) {
|
||||||
|
LauncherEnv.getInput(
|
||||||
|
I18n["instance.copy.prompt"],
|
||||||
|
I18n["instance.copy.details"],
|
||||||
|
InstanceNameTool.getNextValid(data.instance.name),
|
||||||
|
{ s: String? ->
|
||||||
|
try {
|
||||||
|
JFiles.copyRecursive(
|
||||||
|
data.instance.path,
|
||||||
|
MetaHolder.INSTANCE_DIR.resolve(InstanceNameTool.getNextValid(s))
|
||||||
|
)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
LauncherEnv.showError(I18n["instance.copy.fail"], e)
|
||||||
|
}
|
||||||
|
}) { R.nop() }
|
||||||
|
}.iconName = "edit-copy-symbolic"
|
||||||
|
settingsSection.literalButton("delete", I18n["instance.delete"]) {
|
||||||
|
LauncherEnv.showOkCancel(
|
||||||
|
I18n["instance.delete.confirm"],
|
||||||
|
I18n["instance.delete.confirm.title"]
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
JFiles.deleteRecursive(data.instance.path)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
LauncherEnv.showError(I18n["instance.delete.fail"], e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.iconName = "edit-delete-symbolic"
|
||||||
|
|
||||||
|
registerForUnbind(data.launch.onClicked { GtkMenubar.launch(data.instance, LaunchType.Client) })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun UnbindContext.unbind(widget: ActionRow, data: Decomposed) {
|
||||||
|
data.popoverMenu.insertActionGroup(data.instanceId, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ActionContext.castWidget(widget: Widget): ActionRow = widget as ActionRow
|
||||||
|
|
||||||
|
override fun ActionContext.lookup(id: String, widget: ActionRow): Decomposed {
|
||||||
|
val instance = instanceList[id]
|
||||||
|
val prefixes = widget.firstChild!!.firstChild as Box
|
||||||
|
val suffixes = widget.firstChild!!.lastChild as Box
|
||||||
|
val thumbnail = InstanceThumbnail.castFrom(prefixes.firstChild as Stack)
|
||||||
|
val launch = suffixes.firstChild as Button
|
||||||
|
val menuButton = launch.nextSibling as MenuButton
|
||||||
|
val popoverMenu = menuButton.popover as PopoverMenu
|
||||||
|
return Decomposed(listItem, id, instance, thumbnail, launch, popoverMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Decomposed(
|
||||||
|
val item: ListItem,
|
||||||
|
val instanceId: String,
|
||||||
|
val instance: Instance?,
|
||||||
|
val thumbnail: InstanceThumbnail,
|
||||||
|
val launch: Button,
|
||||||
|
val popoverMenu: PopoverMenu
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
|
import org.gnome.gtk.Image
|
||||||
|
import org.gnome.gtk.Spinner
|
||||||
|
import org.gnome.gtk.Stack
|
||||||
|
import java.lang.foreign.MemorySegment
|
||||||
|
|
||||||
|
class InstanceThumbnail : Stack {
|
||||||
|
private constructor(address: MemorySegment) : super(address)
|
||||||
|
|
||||||
|
constructor() : super() {
|
||||||
|
val spinner = Spinner()
|
||||||
|
val image = Image()
|
||||||
|
val generic = Image()
|
||||||
|
spinner.name = SPINNER
|
||||||
|
image.name = IMAGE
|
||||||
|
generic.name = GENERIC
|
||||||
|
generic.setFromIconName("media-playback-start-symbolic") //TODO better default icon
|
||||||
|
addNamed(spinner, SPINNER)
|
||||||
|
addNamed(image, IMAGE)
|
||||||
|
addNamed(generic, GENERIC)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(entry: Instance) {
|
||||||
|
val spinner = getChildByName(SPINNER) as Spinner
|
||||||
|
val image = getChildByName(IMAGE) as Image //TODO
|
||||||
|
val generic = getChildByName(GENERIC) as Image
|
||||||
|
//TODO mark instance being played
|
||||||
|
visibleChild = if (entry.isSetupLocked) {
|
||||||
|
spinner
|
||||||
|
} else if (false) { // if the instance has an image, load the image data and set it as the visible child
|
||||||
|
image
|
||||||
|
} else {
|
||||||
|
generic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SPINNER = "spinner"
|
||||||
|
private const val IMAGE = "image"
|
||||||
|
private const val GENERIC = "generic"
|
||||||
|
|
||||||
|
fun castFrom(stack: Stack): InstanceThumbnail = InstanceThumbnail(stack.handle())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import org.gnome.gtk.DropDown
|
||||||
|
import org.gnome.gtk.PropertyExpression
|
||||||
|
import org.gnome.gtk.StringList
|
||||||
|
import org.gnome.gtk.StringObject
|
||||||
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
import java.util.function.IntConsumer
|
||||||
|
|
||||||
|
class KDropDown<T>(options: Array<T>, private val stringify: (T) -> String, selected: Int): DropDown(options.toModel(stringify), null) {
|
||||||
|
private val onChange = ArrayList<IntConsumer>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.selected = selected
|
||||||
|
onNotify("selected") { _ -> onChange.forEach { it.accept(this.selected) } }
|
||||||
|
expression = PropertyExpression(StringObject.getType(), null, "string")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onChange(changed: IntConsumer) {
|
||||||
|
onChange.add(changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateOptions(newOptions: Array<T>, newSelected: Int) {
|
||||||
|
selected = 0
|
||||||
|
model = newOptions.toModel(stringify)
|
||||||
|
selected = newSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun <T> Array<T>.toModel(stringify: (T) -> String) = StringList(map(stringify).toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun KDropDown(vararg options: @PropertyKey(resourceBundle = I18n.BUNDLE) String, selected: Int = 0): KDropDown<String> =
|
||||||
|
KDropDown(options.map { it }.toTypedArray(), { I18n[it] }, selected)
|
@ -0,0 +1,17 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control
|
||||||
|
|
||||||
|
import org.gnome.gtk.Entry
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
class KEntry(value: String? = ""): Entry() {
|
||||||
|
private val onChange = ArrayList<Consumer<String>>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
text = value ?: ""
|
||||||
|
onChanged { onChange.forEach { it.accept(text) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onChange(changed: Consumer<String>) {
|
||||||
|
onChange.add(changed)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control
|
||||||
|
|
||||||
|
import io.github.jwharm.javagi.gobject.SignalConnection
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import org.gnome.gtk.ListItem
|
||||||
|
import org.gnome.gtk.SignalListItemFactory
|
||||||
|
import org.gnome.gtk.StringObject
|
||||||
|
import org.gnome.gtk.Widget
|
||||||
|
|
||||||
|
abstract class KSignalListItemFactory<TData, TWidget : Widget> : SignalListItemFactory() {
|
||||||
|
private val toDisconnect: MutableMap<String, MutableSet<SignalConnection<*>>> = HashMap()
|
||||||
|
init {
|
||||||
|
onSetup {
|
||||||
|
val li = it as ListItem
|
||||||
|
li.child = setup()
|
||||||
|
}
|
||||||
|
onTeardown {
|
||||||
|
val li = it as ListItem
|
||||||
|
li.child = null
|
||||||
|
}
|
||||||
|
onBind {
|
||||||
|
val li = it as ListItem
|
||||||
|
val id = (li.item as StringObject).string
|
||||||
|
val context = BindContextImpl(id, li)
|
||||||
|
if (li.child == null) {
|
||||||
|
Log.warn("Unexpectedly, list item widget is null, retrying setup")
|
||||||
|
li.child = setup()
|
||||||
|
}
|
||||||
|
val widget = context.castWidget(li.child!!)
|
||||||
|
context.bind(widget, context.lookup(id, widget))
|
||||||
|
}
|
||||||
|
onUnbind {
|
||||||
|
val li = it as ListItem
|
||||||
|
val id = (li.item as StringObject).string
|
||||||
|
val context = UnbindContextImpl(li)
|
||||||
|
val widget = context.castWidget(li.child!!)
|
||||||
|
toDisconnect.remove(id)?.forEach { it.disconnect() }
|
||||||
|
context.unbind(widget, context.lookup(id, widget))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun setup(): TWidget
|
||||||
|
abstract fun BindContext.bind(widget: TWidget, data: TData)
|
||||||
|
abstract fun UnbindContext.unbind(widget: TWidget, data: TData)
|
||||||
|
abstract fun ActionContext.lookup(id: String, widget: TWidget): TData
|
||||||
|
abstract fun ActionContext.castWidget(widget: Widget): TWidget
|
||||||
|
|
||||||
|
interface ActionContext {
|
||||||
|
val listItem: ListItem
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BindContext: ActionContext {
|
||||||
|
fun registerForUnbind(signal: SignalConnection<*>)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UnbindContext: ActionContext {
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class BindContextImpl(private val id: String, override val listItem: ListItem) : BindContext {
|
||||||
|
override fun registerForUnbind(signal: SignalConnection<*>) {
|
||||||
|
toDisconnect.computeIfAbsent(id) { _ -> HashSet() }
|
||||||
|
.add(signal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class UnbindContextImpl(override val listItem: ListItem): UnbindContext
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control
|
||||||
|
|
||||||
|
import org.gnome.gtk.Align
|
||||||
|
import org.gnome.gtk.ProgressBar
|
||||||
|
import org.gnome.gtk.Revealer
|
||||||
|
import org.gnome.gtk.RevealerTransitionType
|
||||||
|
|
||||||
|
class LoadingRevealer: Revealer() {
|
||||||
|
private val progressBar: ProgressBar
|
||||||
|
|
||||||
|
init {
|
||||||
|
transitionType = RevealerTransitionType.CROSSFADE
|
||||||
|
revealChild = false
|
||||||
|
vexpand = false
|
||||||
|
hexpand = false
|
||||||
|
valign = Align.CENTER
|
||||||
|
halign = Align.FILL
|
||||||
|
progressBar = ProgressBar().apply {
|
||||||
|
hexpand = true
|
||||||
|
vexpand = false
|
||||||
|
addCssClass("osd")
|
||||||
|
}
|
||||||
|
child = progressBar
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRunning(state: Boolean) {
|
||||||
|
revealChild = state
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setProgress(progress: Double) {
|
||||||
|
progressBar.fraction = progress.coerceIn(0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pulse() {
|
||||||
|
progressBar.pulse()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.assistant
|
||||||
|
|
||||||
|
import org.gnome.gtk.AssistantPageType
|
||||||
|
import org.gnome.gtk.Box
|
||||||
|
import org.gnome.gtk.Orientation
|
||||||
|
|
||||||
|
class AssistantPage(val title: String, val type: AssistantPageType): Box(Orientation.VERTICAL, 8) {
|
||||||
|
lateinit var setComplete: (Boolean) -> Unit
|
||||||
|
private val onOpen = ArrayList<Runnable>()
|
||||||
|
fun onOpen(action: Runnable) {
|
||||||
|
onOpen.add(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emitOpen() = onOpen.forEach { it.run() }
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.assistant
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import org.gnome.gtk.Application
|
||||||
|
import org.gnome.gtk.Assistant
|
||||||
|
import org.gnome.gtk.AssistantPageType
|
||||||
|
|
||||||
|
open class KAssistant(app: Application) : Assistant() {
|
||||||
|
private val pages = ArrayList<AssistantPage>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
application = app
|
||||||
|
onPrepare { next ->
|
||||||
|
val page = pages.firstOrNull { it.handle() == next.handle() }
|
||||||
|
if (page == null) {
|
||||||
|
Log.error("Unknown page opened in assistant")
|
||||||
|
} else {
|
||||||
|
page.emitOpen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun page(title: String, type: AssistantPageType, setup: AssistantPage.() -> Unit): Int {
|
||||||
|
val page = AssistantPage(title, type)
|
||||||
|
val idx = appendPage(page)
|
||||||
|
pages.add(page)
|
||||||
|
page.setComplete = { setPageComplete(page, it) }
|
||||||
|
page.setup()
|
||||||
|
setPageType(page, page.type)
|
||||||
|
setPageTitle(page, page.title)
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.settings
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.KDropDown
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.KEntry
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.margin
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import java.util.function.DoubleConsumer
|
||||||
|
import java.util.function.IntConsumer
|
||||||
|
|
||||||
|
class IRow(
|
||||||
|
title: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
|
||||||
|
subtitle: @PropertyKey(resourceBundle = I18n.BUNDLE) String?,
|
||||||
|
vararg args: Any?
|
||||||
|
) : Box(Orientation.HORIZONTAL, 40) {
|
||||||
|
init {
|
||||||
|
margin = 8
|
||||||
|
val head: Widget
|
||||||
|
val lab = ILabel(title, *args)
|
||||||
|
lab.halign = Align.START
|
||||||
|
if (subtitle != null) {
|
||||||
|
val headB = Box(Orientation.VERTICAL, 0)
|
||||||
|
headB.append(lab)
|
||||||
|
val lab1 = ILabel(subtitle, ILabel.Mode.SUBTITLE, *args)
|
||||||
|
lab1.halign = Align.START
|
||||||
|
headB.append(lab1)
|
||||||
|
head = headB
|
||||||
|
} else {
|
||||||
|
head = lab
|
||||||
|
}
|
||||||
|
head.halign = Align.START
|
||||||
|
head.valign = Align.CENTER
|
||||||
|
append(head)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setButton(text: @PropertyKey(resourceBundle = I18n.BUNDLE) String, action: Button.ClickedCallback?): Button =
|
||||||
|
Button.withLabel(I18n[text]).apply {
|
||||||
|
packSmallEnd()
|
||||||
|
onClicked(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDropdown(options: Array<String>, defaultIndex: Int, changed: IntConsumer): KDropDown<String> =
|
||||||
|
KDropDown(options, { it } , defaultIndex).apply {
|
||||||
|
onChange(changed)
|
||||||
|
packSmallEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSwitch(value: Boolean, changed: Consumer<Boolean>): Switch =
|
||||||
|
Switch().apply {
|
||||||
|
packSmallEnd()
|
||||||
|
active = value
|
||||||
|
onStateSet { state: Boolean ->
|
||||||
|
changed.accept(state)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSpinButton(value: Double, min: Double, max: Double, step: Double, changed: DoubleConsumer): SpinButton =
|
||||||
|
SpinButton.withRange(min, max, step).apply {
|
||||||
|
packSmallEnd()
|
||||||
|
this.value = value
|
||||||
|
onValueChanged { changed.accept(this.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEntry(value: String?, changed: Consumer<String>): KEntry =
|
||||||
|
KEntry(value).apply {
|
||||||
|
hexpand = true
|
||||||
|
valign = Align.CENTER
|
||||||
|
halign = Align.FILL
|
||||||
|
onChange(changed)
|
||||||
|
append(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Widget.packSmallEnd() {
|
||||||
|
firstChild!!.hexpand = true
|
||||||
|
valign = Align.CENTER
|
||||||
|
halign = Align.END
|
||||||
|
this@IRow.append(this)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.settings
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.marginHorizontal
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
open class SectionedSettingsTab<W : Window>(window: W?): SettingsTab<Box, W>(window, Box(Orientation.VERTICAL, 8)) {
|
||||||
|
init {
|
||||||
|
content.marginHorizontal = 24
|
||||||
|
content.marginTop = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
fun append(child: Widget) = content.append(child)
|
||||||
|
|
||||||
|
protected fun section(title: @PropertyKey(resourceBundle = I18n.BUNDLE) String?, builder: Section.() -> Unit) {
|
||||||
|
if (title != null) append(ILabel(title, ILabel.Mode.HEADING))
|
||||||
|
val frame = Frame(null as String?)
|
||||||
|
val listBox = ListBox()
|
||||||
|
listBox.selectionMode = SelectionMode.NONE
|
||||||
|
listBox.showSeparators = true
|
||||||
|
frame.child = listBox
|
||||||
|
val count = AtomicInteger(0)
|
||||||
|
builder(object : Section {
|
||||||
|
override fun row(title: String, subtitle: String?, vararg args: Any?, build: IRow.() -> Unit): IRow {
|
||||||
|
val row = IRow(title, subtitle, *args)
|
||||||
|
row(row)
|
||||||
|
build(row)
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun row(row: Widget): ListBoxRow {
|
||||||
|
listBox.append(row)
|
||||||
|
return listBox.getRowAtIndex(count.getAndIncrement())!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(row: Widget) {
|
||||||
|
listBox.remove(row)
|
||||||
|
count.decrementAndGet()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
var i = 0
|
||||||
|
val len = count.getAndSet(0)
|
||||||
|
while (i < len) {
|
||||||
|
listBox.remove(listBox.getRowAtIndex(0))
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
append(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Section {
|
||||||
|
fun row(
|
||||||
|
title: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
|
||||||
|
subtitle: @PropertyKey(resourceBundle = I18n.BUNDLE) String?,
|
||||||
|
vararg args: Any?
|
||||||
|
): IRow = row(title, subtitle, *args, build = {})
|
||||||
|
|
||||||
|
fun row(
|
||||||
|
title: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
|
||||||
|
subtitle: @PropertyKey(resourceBundle = I18n.BUNDLE) String?,
|
||||||
|
vararg args: Any?,
|
||||||
|
build: IRow.() -> Unit
|
||||||
|
): IRow
|
||||||
|
|
||||||
|
fun row(row: Widget): ListBoxRow?
|
||||||
|
fun remove(row: Widget)
|
||||||
|
fun clear()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.settings
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.StringFormatter
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend
|
||||||
|
import org.gnome.gtk.Widget
|
||||||
|
import org.gnome.gtk.Window
|
||||||
|
|
||||||
|
open class SettingsTab<T : Widget, W : Window>(
|
||||||
|
protected val window: W?,
|
||||||
|
val content: T
|
||||||
|
) {
|
||||||
|
protected fun showError(message: String, t: Throwable) =
|
||||||
|
GtkEnvBackend.simpleDialog(
|
||||||
|
window,
|
||||||
|
StringFormatter.toString(t),
|
||||||
|
message,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.control.settings
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import org.gnome.adw.HeaderBar
|
||||||
|
import org.gnome.adw.ViewStack
|
||||||
|
import org.gnome.adw.ViewSwitcherBar
|
||||||
|
import org.gnome.adw.ViewSwitcherTitle
|
||||||
|
import org.gnome.gobject.BindingFlags
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
|
||||||
|
open class SettingsWindow(app: Application?) : Window() {
|
||||||
|
protected val stack: ViewStack
|
||||||
|
|
||||||
|
init {
|
||||||
|
application = app
|
||||||
|
|
||||||
|
stack = ViewStack()
|
||||||
|
|
||||||
|
val header = HeaderBar()
|
||||||
|
val viewSwitcher = ViewSwitcherTitle()
|
||||||
|
viewSwitcher.stack = stack
|
||||||
|
header.titleWidget = viewSwitcher
|
||||||
|
titlebar = header
|
||||||
|
|
||||||
|
val scroll = ScrolledWindow()
|
||||||
|
scroll.setPolicy(PolicyType.NEVER, PolicyType.AUTOMATIC)
|
||||||
|
scroll.child = stack
|
||||||
|
scroll.vexpand = true
|
||||||
|
|
||||||
|
val bottomBar = ViewSwitcherBar()
|
||||||
|
bottomBar.stack = stack
|
||||||
|
viewSwitcher.bindProperty("title-visible", bottomBar, "reveal", BindingFlags.DEFAULT)
|
||||||
|
val view = Box(Orientation.VERTICAL, 0)
|
||||||
|
view.append(scroll)
|
||||||
|
view.append(bottomBar)
|
||||||
|
|
||||||
|
child = view
|
||||||
|
|
||||||
|
setDefaultSize(720, 360)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addTab(tab: SettingsTab<*, *>, title: @PropertyKey(resourceBundle = I18n.BUNDLE) String, iconName: String) {
|
||||||
|
stack.addTitledWithIcon(tab.content, title, I18n[title], iconName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var activePage: String
|
||||||
|
get() = stack.visibleChildName!!
|
||||||
|
set(@PropertyKey(resourceBundle = I18n.BUNDLE) title) { stack.visibleChildName = title }
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.menu
|
||||||
|
|
||||||
|
import org.gnome.gio.MenuItem
|
||||||
|
import org.gnome.gio.SimpleAction
|
||||||
|
|
||||||
|
class BuiltButtonItem(action: SimpleAction, menuItem: MenuItem?) : BuiltMenuItem(action, menuItem)
|
@ -0,0 +1,23 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.menu
|
||||||
|
|
||||||
|
import org.gnome.gio.MenuItem
|
||||||
|
import org.gnome.gio.SimpleAction
|
||||||
|
import org.gnome.gio.ThemedIcon
|
||||||
|
|
||||||
|
abstract class BuiltMenuItem protected constructor(action: SimpleAction, protected val menuItem: MenuItem?) {
|
||||||
|
protected val action: SimpleAction
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
var enabled: Boolean
|
||||||
|
get() = action.enabled
|
||||||
|
set(enabled) {
|
||||||
|
action.enabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
var iconName: String?
|
||||||
|
set(iconName) { menuItem!!.setIcon(ThemedIcon(iconName)) }
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.menu
|
||||||
|
|
||||||
|
import org.gnome.gio.SimpleAction
|
||||||
|
import org.gnome.glib.Variant
|
||||||
|
|
||||||
|
class BuiltRadioItem<T>(action: SimpleAction, private val options: List<T>) : BuiltMenuItem(action, null) {
|
||||||
|
var selected: T
|
||||||
|
get() = options[action.state!!.int32]
|
||||||
|
set(selected) {
|
||||||
|
action.state = Variant.int32(options.indexOf(selected))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.menu
|
||||||
|
|
||||||
|
import org.gnome.gio.MenuItem
|
||||||
|
import org.gnome.gio.SimpleAction
|
||||||
|
import org.gnome.glib.Variant
|
||||||
|
|
||||||
|
class BuiltToggleItem(action: SimpleAction, menuItem: MenuItem?) : BuiltMenuItem(action, menuItem) {
|
||||||
|
var state: Boolean
|
||||||
|
get() = action.state!!.boolean
|
||||||
|
set(state) {
|
||||||
|
action.state = Variant.boolean_(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggle(): Boolean {
|
||||||
|
val toggled = !state
|
||||||
|
state = toggled
|
||||||
|
return toggled
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.menu
|
||||||
|
|
||||||
|
import io.github.jwharm.javagi.glib.types.VariantTypes
|
||||||
|
import io.gitlab.jfronny.commons.throwable.ThrowingRunnable
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import org.gnome.gio.*
|
||||||
|
import org.gnome.glib.Variant
|
||||||
|
import org.gnome.gtk.Application
|
||||||
|
import org.gnome.gtk.MenuButton
|
||||||
|
import org.gnome.gtk.PopoverMenu
|
||||||
|
import java.util.function.BiFunction
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
class MenuBuilder private constructor(map: ActionMap, menu: Menu, prefix: String, groupName: String) {
|
||||||
|
private val map: ActionMap
|
||||||
|
private val refs: MutableMap<String, Action> = LinkedHashMap()
|
||||||
|
val menu: Menu
|
||||||
|
private val prefix: String
|
||||||
|
private val groupName: String
|
||||||
|
|
||||||
|
constructor(menu: PopoverMenu, groupName: String) : this(
|
||||||
|
insertMap(menu, groupName),
|
||||||
|
menu.menuModel as Menu,
|
||||||
|
"",
|
||||||
|
groupName
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor(app: Application, menu: Menu = getRootMenu(app), prefix: String = "") : this(app, menu, prefix, "app.")
|
||||||
|
|
||||||
|
init {
|
||||||
|
fun String.suffix() = if (isNotEmpty() && !endsWith(".")) "$this." else this
|
||||||
|
|
||||||
|
this.map = map
|
||||||
|
this.menu = menu
|
||||||
|
this.prefix = prefix.suffix()
|
||||||
|
this.groupName = groupName.suffix()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun button(name: String, onClick: ThrowingRunnable<*>): BuiltButtonItem {
|
||||||
|
return literalButton(name, I18n["menu.$prefix$name"], onClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun literalButton(internalName: String, label: String?, onClick: ThrowingRunnable<*>): BuiltButtonItem {
|
||||||
|
var internalName = internalName
|
||||||
|
internalName = prefix + internalName
|
||||||
|
val action = SimpleAction(internalName, null)
|
||||||
|
addAction(internalName, action)
|
||||||
|
action.onActivate { _ ->
|
||||||
|
try {
|
||||||
|
onClick.run()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.error("Could not execute action", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val menuItem = MenuItem(label, groupName + internalName)
|
||||||
|
menu.appendItem(menuItem)
|
||||||
|
action.enabled = true
|
||||||
|
return BuiltButtonItem(action, menuItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggle(name: String, initial: Boolean, onToggle: Consumer<Boolean?>): BuiltToggleItem {
|
||||||
|
var name = name
|
||||||
|
name = prefix + name
|
||||||
|
val action = SimpleAction.stateful(name, null, Variant.boolean_(initial))
|
||||||
|
addAction(name, action)
|
||||||
|
action.onActivate { _ ->
|
||||||
|
val state = !action.getState()!!.boolean
|
||||||
|
action.state = Variant.boolean_(state)
|
||||||
|
onToggle.accept(state)
|
||||||
|
}
|
||||||
|
val menuItem = MenuItem(I18n["menu.$name"], groupName + name)
|
||||||
|
menu.appendItem(menuItem)
|
||||||
|
return BuiltToggleItem(action, menuItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> radio(name: String, initial: T, options: List<T>, onCheck: Consumer<T>): BuiltRadioItem<T> {
|
||||||
|
return literalRadio(name, initial, options, { i, _ -> I18n["menu.$prefix$name", i] }, onCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> literalRadio(
|
||||||
|
name: String,
|
||||||
|
initial: T,
|
||||||
|
options: List<T>,
|
||||||
|
stringifier: BiFunction<Int, T, String?>,
|
||||||
|
onCheck: Consumer<T>
|
||||||
|
): BuiltRadioItem<T> {
|
||||||
|
var name = name
|
||||||
|
name = prefix + name
|
||||||
|
val action = SimpleAction.stateful(name, VariantTypes.INT32, Variant.int32(options.indexOf(initial)))
|
||||||
|
addAction(name, action)
|
||||||
|
action.onActivate { variant: Variant? ->
|
||||||
|
action.state = variant
|
||||||
|
onCheck.accept(options[variant!!.int32])
|
||||||
|
}
|
||||||
|
for ((i, option) in options.withIndex()) {
|
||||||
|
menu.appendItem(MenuItem(stringifier.apply(i, option), "$groupName$name($i)"))
|
||||||
|
}
|
||||||
|
return BuiltRadioItem(action, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun submenu(name: String): MenuBuilder {
|
||||||
|
return literalSubmenu(name, I18n["menu.$prefix$name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun literalSubmenu(name: String, label: String?): MenuBuilder {
|
||||||
|
var name = name
|
||||||
|
name = prefix + name
|
||||||
|
val submenu = Menu()
|
||||||
|
menu.appendSubmenu(label, submenu)
|
||||||
|
return MenuBuilder(map, submenu, name, groupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun section(name: String): MenuBuilder {
|
||||||
|
return literalSection(name, I18n["section.$prefix$name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun literalSection(name: String, label: String?): MenuBuilder {
|
||||||
|
var name = name
|
||||||
|
name = prefix + name
|
||||||
|
val section = Menu()
|
||||||
|
menu.appendSection(label, section)
|
||||||
|
return MenuBuilder(map, section, name, groupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
menu.removeAll()
|
||||||
|
refs.forEach { (name, _) -> map.removeAction(name) }
|
||||||
|
refs.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addAction(name: String, action: SimpleAction) {
|
||||||
|
map.addAction(action)
|
||||||
|
refs[name] = action
|
||||||
|
}
|
||||||
|
|
||||||
|
fun asPopover(): PopoverMenu {
|
||||||
|
return PopoverMenu.fromModel(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOCK = Any()
|
||||||
|
private fun getRootMenu(app: Application): Menu {
|
||||||
|
synchronized(LOCK) {
|
||||||
|
val currentMenu = app.menubar
|
||||||
|
return if (currentMenu == null) {
|
||||||
|
val menu = Menu()
|
||||||
|
app.menubar = menu
|
||||||
|
menu
|
||||||
|
} else {
|
||||||
|
currentMenu as Menu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(target: MenuButton, groupName: String): MenuBuilder {
|
||||||
|
val menu = Menu()
|
||||||
|
val pm = PopoverMenu.fromModel(menu)
|
||||||
|
target.setPopover(pm)
|
||||||
|
return MenuBuilder(pm, groupName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun insertMap(menu: PopoverMenu, groupName: String): ActionMap {
|
||||||
|
val ag = SimpleActionGroup()
|
||||||
|
menu.insertActionGroup(groupName, ag)
|
||||||
|
return ag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.PropertyKey
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object I18n {
|
||||||
|
const val BUNDLE = "inceptum"
|
||||||
|
private val bundle = ResourceBundle.getBundle(BUNDLE)
|
||||||
|
|
||||||
|
operator fun get(key: @PropertyKey(resourceBundle = BUNDLE) String): String =
|
||||||
|
bundle.getString(key)
|
||||||
|
|
||||||
|
operator fun get(key: @PropertyKey(resourceBundle = BUNDLE) String, vararg args: Any?): String =
|
||||||
|
String.format(bundle.getString(key), *args)
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
|
|
||||||
|
operator fun List<Instance>.get(id: String) = firstOrNull { it.id == id }
|
@ -0,0 +1,37 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus
|
||||||
|
import io.gitlab.jfronny.inceptum.common.Utils
|
||||||
|
import org.gnome.glib.LogField
|
||||||
|
import org.gnome.glib.LogLevelFlags
|
||||||
|
import org.gnome.glib.LogWriterFunc
|
||||||
|
import org.gnome.glib.LogWriterOutput
|
||||||
|
|
||||||
|
object Log : SystemLoggerPlus by Utils.LOGGER
|
||||||
|
object GtkLog : LogWriterFunc {
|
||||||
|
// Based on https://github.com/jwharm/java-gi-examples/blob/6e08b4d2e62998c53d855b32433513308e01e82d/Logging/src/main/java/io/github/jwharm/javagi/examples/logging/SLF4JLogWriterFunc.java
|
||||||
|
// Original license: LGPL-2.1, written by Jan-Willem Harmannij
|
||||||
|
override fun run(logLevel: Set<LogLevelFlags>, fields: Array<LogField>): LogWriterOutput {
|
||||||
|
val level = logLevel.firstOrNull()?.let { convertLevel(it) } ?: return LogWriterOutput.UNHANDLED
|
||||||
|
val domain = readStringFromKey("GLIB_DOMAIN", fields) ?: return LogWriterOutput.UNHANDLED
|
||||||
|
val message = readStringFromKey("MESSAGE", fields) ?: return LogWriterOutput.UNHANDLED
|
||||||
|
val source = readStringFromKey("CODE_FILE", fields) ?: "<unknown>"
|
||||||
|
val func = readStringFromKey("CODE_FUNC", fields)?.let { "::$it" } ?: ""
|
||||||
|
val line = readStringFromKey("CODE_LINE", fields)?.let { ":$it" } ?: ""
|
||||||
|
System.getLogger(domain).log(level, "$source$func$line: $message")
|
||||||
|
return LogWriterOutput.HANDLED
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertLevel(flag: LogLevelFlags) = when (flag) {
|
||||||
|
LogLevelFlags.LEVEL_ERROR, LogLevelFlags.LEVEL_CRITICAL -> System.Logger.Level.ERROR
|
||||||
|
LogLevelFlags.LEVEL_WARNING -> System.Logger.Level.WARNING
|
||||||
|
LogLevelFlags.LEVEL_MESSAGE, LogLevelFlags.LEVEL_INFO -> System.Logger.Level.INFO
|
||||||
|
LogLevelFlags.LEVEL_DEBUG -> System.Logger.Level.DEBUG
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
private fun readStringFromKey(key: String, logFields: Array<LogField>) = logFields.firstOrNull { it.readKey() == key }?.readString()
|
||||||
|
private fun LogField.readString(): String {
|
||||||
|
val length = readLength()
|
||||||
|
return readValue().reinterpret(if (length == -1L) Long.MAX_VALUE else length).getString(0)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.OSUtils
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
object Memory {
|
||||||
|
const val KB: Long = 1024
|
||||||
|
const val MB = KB * 1024
|
||||||
|
const val GB = MB * 1024
|
||||||
|
private val impl = when (OSUtils.TYPE) {
|
||||||
|
OSUtils.Type.LINUX -> LinuxMI
|
||||||
|
OSUtils.Type.WINDOWS -> WindowsMI
|
||||||
|
OSUtils.Type.MAC_OS -> MacOsMI
|
||||||
|
}
|
||||||
|
private val totalMemory by lazy { impl.getTotalMemory() }
|
||||||
|
val maxMBForInstance: Long get() = (totalMemory / MB - 1024).coerceAtLeast(1024)
|
||||||
|
|
||||||
|
private interface MI {
|
||||||
|
fun getTotalMemory(): Long
|
||||||
|
}
|
||||||
|
|
||||||
|
private object LinuxMI : MI {
|
||||||
|
override fun getTotalMemory(): Long {
|
||||||
|
try {
|
||||||
|
Files.lines(Path.of("/proc/meminfo")).use { stream ->
|
||||||
|
val memTotal = stream
|
||||||
|
.filter { s: String -> s.startsWith("MemTotal:") }
|
||||||
|
.map { s: String -> s.substring("MemTotal:".length) }
|
||||||
|
.map { obj: String -> obj.trim { it <= ' ' } }
|
||||||
|
.findFirst()
|
||||||
|
return if (memTotal.isPresent()) {
|
||||||
|
parseDecimalMemorySizeToBinary(memTotal.get())
|
||||||
|
} else {
|
||||||
|
Log.error("Could not find total memory")
|
||||||
|
32 * GB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error("Could not get total memory", e)
|
||||||
|
return 32 * GB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from oshi
|
||||||
|
private val BYTES_PATTERN = Pattern.compile("(\\d+) ?([kKMGT]?B?).*")
|
||||||
|
private val WHITESPACES = Pattern.compile("\\s+")
|
||||||
|
private fun parseDecimalMemorySizeToBinary(size: String): Long {
|
||||||
|
var mem = WHITESPACES.split(size)
|
||||||
|
if (mem.size < 2) {
|
||||||
|
// If no spaces, use regexp
|
||||||
|
val matcher = BYTES_PATTERN.matcher(size.trim { it <= ' ' })
|
||||||
|
if (matcher.find() && matcher.groupCount() == 2) {
|
||||||
|
mem = arrayOfNulls(2)
|
||||||
|
mem[0] = matcher.group(1)
|
||||||
|
mem[1] = matcher.group(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var capacity = parseLongOrDefault(mem[0], 0L)
|
||||||
|
if (mem.size == 2 && mem[1]!!.length > 1) {
|
||||||
|
when (mem[1]!![0]) {
|
||||||
|
'T' -> capacity = capacity shl 40
|
||||||
|
'G' -> capacity = capacity shl 30
|
||||||
|
'M' -> capacity = capacity shl 20
|
||||||
|
'K', 'k' -> capacity = capacity shl 10
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseLongOrDefault(s: String, defaultLong: Long): Long = try {
|
||||||
|
s.toLong()
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
defaultLong
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object WindowsMI : MI {
|
||||||
|
override fun getTotalMemory(): Long {
|
||||||
|
return 32 * GB // This is currently unsupported, but any implementations by Windows user using panama are welcome
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object MacOsMI : MI {
|
||||||
|
override fun getTotalMemory(): Long {
|
||||||
|
return 32 * GB // This is currently unsupported, but any implementations by MacOS user using panama are welcome
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
inline fun <reified T> Stream<T>.toTypedArray(): Array<T> = toArray(::arrayOfNulls)
|
@ -0,0 +1,10 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import org.gnome.gio.ListModel
|
||||||
|
import org.gnome.gtk.StringList
|
||||||
|
|
||||||
|
fun StringList.clear() = splice(0, size, null)
|
||||||
|
fun StringList.addAll(values: Array<String>) = splice(size, 0, values)
|
||||||
|
fun StringList.replaceAll(values: Array<String>) = splice(0, size, values)
|
||||||
|
|
||||||
|
val ListModel.size get() = nItems
|
@ -0,0 +1,46 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.util
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
|
||||||
|
import org.gnome.adw.ActionRow
|
||||||
|
import org.gnome.gtk.EntryBuffer
|
||||||
|
import org.gnome.gtk.Label
|
||||||
|
import org.gnome.gtk.MessageDialog
|
||||||
|
import org.gnome.gtk.Widget
|
||||||
|
|
||||||
|
var Widget.margin: Int
|
||||||
|
set(value) {
|
||||||
|
marginVertical = value
|
||||||
|
marginHorizontal = value
|
||||||
|
}
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
|
||||||
|
var Widget.marginVertical: Int
|
||||||
|
set(value) {
|
||||||
|
marginTop = value
|
||||||
|
marginBottom = value
|
||||||
|
}
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
|
||||||
|
var Widget.marginHorizontal: Int
|
||||||
|
set(value) {
|
||||||
|
marginStart = value
|
||||||
|
marginEnd = value
|
||||||
|
}
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
|
||||||
|
var MessageDialog.markup: String
|
||||||
|
set(value) { setMarkup(value.escapedMarkup) }
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
|
||||||
|
var Label.markup: String
|
||||||
|
set(value) { setMarkup(value.escapedMarkup) }
|
||||||
|
get() = throw NotImplementedError()
|
||||||
|
|
||||||
|
val String.escapedMarkup: String
|
||||||
|
get() = replace(unmatchedAnd, "&")
|
||||||
|
|
||||||
|
private val unmatchedAnd = Regex("&(?![a-z]{1,6};|#[0-9]+;|#x[0-9A-F]+;)")
|
||||||
|
|
||||||
|
fun ActionRow.fixSubtitle() = ILabel.theme(firstChild!!.lastChild!!.prevSibling!!.lastChild as Label, ILabel.Mode.SUBTITLE)
|
||||||
|
|
||||||
|
fun EntryBuffer.clear() = deleteText(0, length)
|
@ -0,0 +1,30 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.window
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.common.BuildMetadata
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import org.gnome.gtk.AboutDialog
|
||||||
|
import org.gnome.gtk.License
|
||||||
|
|
||||||
|
class AboutWindow : AboutDialog() {
|
||||||
|
init {
|
||||||
|
programName = "Inceptum"
|
||||||
|
copyright = "Copyright (C) 2021-2023 JFronny"
|
||||||
|
version = BuildMetadata.VERSION
|
||||||
|
licenseType = License.MIT_X11
|
||||||
|
license = I18n["about.license"]
|
||||||
|
websiteLabel = I18n["about.contact"]
|
||||||
|
website = "https://jfronny.gitlab.io/contact.html"
|
||||||
|
if (!BuildMetadata.IS_PUBLIC) {
|
||||||
|
comments = I18n["about.unsupported-build"]
|
||||||
|
}
|
||||||
|
val vm = Runtime.version().feature()
|
||||||
|
systemInformation = I18n[if (BuildMetadata.VM_VERSION == vm) "about.jvm" else "about.jvm.unsupported", vm]
|
||||||
|
//TODO setLogo
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun createAndShow() {
|
||||||
|
AboutWindow().visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,166 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.window
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.inceptum.common.Utils
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.GtkMenubar
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.InstanceListEntryFactory
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.*
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.settings.launcher.LauncherSettingsWindow
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceListWatcher
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
|
||||||
|
import org.gnome.adw.Clamp
|
||||||
|
import org.gnome.adw.StatusPage
|
||||||
|
import org.gnome.gio.*
|
||||||
|
import org.gnome.glib.GLib
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
import org.gnome.gtk.Application
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
class MainWindow(app: Application) : ApplicationWindow(app) {
|
||||||
|
// private val listButton: Button
|
||||||
|
// private val gridButton: Button
|
||||||
|
private val stack: Stack
|
||||||
|
private val empty: StatusPage
|
||||||
|
private val listContainer: Clamp
|
||||||
|
// private val gridView: GridView
|
||||||
|
private val instanceList: MutableList<Instance>
|
||||||
|
private val instanceListModel: StringList
|
||||||
|
|
||||||
|
init {
|
||||||
|
val header = HeaderBar()
|
||||||
|
val newButton = MenuButton()
|
||||||
|
newButton.iconName = "list-add-symbolic"
|
||||||
|
newButton.menuModel = GtkMenubar.newMenu!!.menu
|
||||||
|
val accountsButton = MenuButton()
|
||||||
|
accountsButton.iconName = "avatar-default-symbolic"
|
||||||
|
accountsButton.menuModel = GtkMenubar.accountsMenu!!.menu
|
||||||
|
// listButton = Button.newFromIconName("view-list-symbolic")
|
||||||
|
// listButton.onClicked {
|
||||||
|
// InceptumConfig.listView = true
|
||||||
|
// InceptumConfig.saveConfig()
|
||||||
|
// generateWindowBody()
|
||||||
|
// }
|
||||||
|
// gridButton = Button.newFromIconName("view-grid-symbolic")
|
||||||
|
// gridButton.onClicked {
|
||||||
|
// InceptumConfig.listView = false
|
||||||
|
// InceptumConfig.saveConfig()
|
||||||
|
// generateWindowBody()
|
||||||
|
// }
|
||||||
|
|
||||||
|
//TODO search button like boxes
|
||||||
|
|
||||||
|
val uiMenu = MenuBuilder(app, Menu(), "hamburger")
|
||||||
|
uiMenu.button("support") { Utils.openWebBrowser(URI("https://git.frohnmeyer-wds.de/JfMods/Inceptum/issues")) }
|
||||||
|
uiMenu.button("preferences") { LauncherSettingsWindow(app).visible = true }
|
||||||
|
uiMenu.button("about") { AboutWindow.createAndShow() }
|
||||||
|
val menuButton = MenuButton()
|
||||||
|
menuButton.iconName = "open-menu-symbolic"
|
||||||
|
menuButton.menuModel = uiMenu.menu
|
||||||
|
|
||||||
|
header.packStart(newButton)
|
||||||
|
|
||||||
|
header.packEnd(menuButton)
|
||||||
|
// header.packEnd(gridButton)
|
||||||
|
// header.packEnd(listButton)
|
||||||
|
header.packEnd(accountsButton)
|
||||||
|
|
||||||
|
instanceList = ArrayList()
|
||||||
|
instanceListModel = StringList(arrayOf())
|
||||||
|
val selection = NoSelection(instanceListModel)
|
||||||
|
|
||||||
|
val listView = ListView(selection, InstanceListEntryFactory(app, instanceList))
|
||||||
|
listView.addCssClass("rich-list")
|
||||||
|
listView.showSeparators = true
|
||||||
|
listView.onActivate { position: Int ->
|
||||||
|
// Double click
|
||||||
|
GtkMenubar.launch(instanceList[position], LaunchType.Client)
|
||||||
|
}
|
||||||
|
val frame = Frame(null as String?)
|
||||||
|
frame.child = listView
|
||||||
|
frame.marginHorizontal = 24
|
||||||
|
frame.marginVertical = 12
|
||||||
|
frame.valign = Align.START
|
||||||
|
listContainer = Clamp()
|
||||||
|
listContainer.maximumSize = 900
|
||||||
|
listContainer.child = frame
|
||||||
|
// gridView = GridView(selection, InstanceGridEntryFactory(instanceList))
|
||||||
|
empty = StatusPage()
|
||||||
|
empty.title = I18n["main.empty.title"]
|
||||||
|
empty.description = I18n["main.empty.description"]
|
||||||
|
//TODO empty.setIconName(new Str());
|
||||||
|
stack = Stack()
|
||||||
|
stack.addChild(listContainer)
|
||||||
|
// stack.addChild(gridView)
|
||||||
|
stack.addChild(empty)
|
||||||
|
|
||||||
|
val scroll = ScrolledWindow()
|
||||||
|
scroll.setPolicy(PolicyType.NEVER, PolicyType.AUTOMATIC)
|
||||||
|
scroll.child = stack
|
||||||
|
|
||||||
|
setDefaultSize(720, 360)
|
||||||
|
title = "Inceptum"
|
||||||
|
titlebar = header
|
||||||
|
showMenubar = false
|
||||||
|
child = scroll
|
||||||
|
|
||||||
|
generateWindowBody()
|
||||||
|
//TODO DropTarget to add mods/instances
|
||||||
|
|
||||||
|
try {
|
||||||
|
setupDirWatcher()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error(
|
||||||
|
"Could not set up watch service, live updates of the instance dir will be unavailable",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun setupDirWatcher() {
|
||||||
|
val isw = InstanceListWatcher()
|
||||||
|
addTickCallback { _, _ ->
|
||||||
|
try {
|
||||||
|
if (isw.poll()) generateWindowBody()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error("Could not run update task", e)
|
||||||
|
}
|
||||||
|
GLib.SOURCE_CONTINUE
|
||||||
|
}
|
||||||
|
onCloseRequest {
|
||||||
|
try {
|
||||||
|
isw.close()
|
||||||
|
} catch (ignored: IOException) {}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateWindowBody() {
|
||||||
|
// listButton.visible = !InceptumConfig.listView
|
||||||
|
// gridButton.visible = InceptumConfig.listView
|
||||||
|
try {
|
||||||
|
// Unbind then clear
|
||||||
|
instanceListModel.clear()
|
||||||
|
instanceList.clear()
|
||||||
|
|
||||||
|
// Add new entries
|
||||||
|
instanceList.addAll(InstanceList.ordered())
|
||||||
|
instanceListModel.addAll(instanceList.map { it.id }.toTypedArray())
|
||||||
|
|
||||||
|
// Choose view for this amount of entries
|
||||||
|
// stack.visibleChild = if (InstanceList.isEmpty()) empty
|
||||||
|
// else if (InceptumConfig.listView) listContainer
|
||||||
|
// else gridView
|
||||||
|
stack.visibleChild = if (InstanceList.isEmpty()) empty else listContainer
|
||||||
|
|
||||||
|
// This is called from a tick callback, so re-render
|
||||||
|
stack.queueResize()
|
||||||
|
stack.queueDraw()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.error("Could not generate window body", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,182 @@
|
|||||||
|
package io.gitlab.jfronny.inceptum.gtk.window.create
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.StringFormatter
|
||||||
|
import io.gitlab.jfronny.inceptum.common.InceptumConfig
|
||||||
|
import io.gitlab.jfronny.inceptum.common.MetaHolder
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.backgroundTask
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.KDropDown
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.KEntry
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.assistant.KAssistant
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.schedule
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.markup
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.toTypedArray
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.FabricMetaApi
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.McApi
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.model.fabric.FabricVersionLoaderInfo
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.model.mojang.VersionsListInfo
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceNameTool
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.LoaderInfo
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.setup.SetupStepInfo
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps
|
||||||
|
import org.gnome.glib.GLib
|
||||||
|
import org.gnome.gtk.*
|
||||||
|
|
||||||
|
|
||||||
|
class NewInstanceWindow(app: Application) : KAssistant(app) {
|
||||||
|
companion object {
|
||||||
|
private val VERSIONS = McApi.getVersions()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
var gameVersion: VersionsListInfo? = null
|
||||||
|
var useFabric = false
|
||||||
|
var fabricVersion: FabricVersionLoaderInfo? = null
|
||||||
|
var name = "New Instance"
|
||||||
|
|
||||||
|
var failureMessage = "Unknown error, please look at the log!"
|
||||||
|
var isFailure = false
|
||||||
|
|
||||||
|
page("Welcome", AssistantPageType.INTRO) {
|
||||||
|
append(Label("This assistant will guide you through the process of setting up a Minecraft instance.\nTo begin, please choose the game version you want to use"))
|
||||||
|
|
||||||
|
val versions = VERSIONS.versions.stream()
|
||||||
|
.filter { InceptumConfig.snapshots || it.type == "release" }
|
||||||
|
.toTypedArray()
|
||||||
|
val def = versions.withIndex().firstOrNull { it.value.id == VERSIONS.latest.release }?.index ?: 0
|
||||||
|
gameVersion = versions[def]
|
||||||
|
|
||||||
|
append(KDropDown(versions, { it.id }, def).apply {
|
||||||
|
onChange { gameVersion = versions[it] }
|
||||||
|
})
|
||||||
|
|
||||||
|
setComplete(true)
|
||||||
|
}
|
||||||
|
page("Loader", AssistantPageType.CONTENT) {
|
||||||
|
append(Label("Select a mod loader if you want to use mods in this instance. This can be changed later."))
|
||||||
|
var lastGameVersion: VersionsListInfo? = null
|
||||||
|
var versions = arrayOf<FabricVersionLoaderInfo>()
|
||||||
|
var def = 0
|
||||||
|
|
||||||
|
val none = CheckButton.withLabel("None")
|
||||||
|
val fabric = CheckButton.withLabel("Fabric")
|
||||||
|
none.onToggled { useFabric = false }
|
||||||
|
none.onToggled { useFabric = true }
|
||||||
|
fabric.setGroup(none)
|
||||||
|
append(none)
|
||||||
|
val fabricVersionDropdown = KDropDown(versions, { it.loader.version }, def)
|
||||||
|
fabricVersionDropdown.onChange { fabricVersion = versions[it] }
|
||||||
|
append(Box(Orientation.HORIZONTAL, 8).apply {
|
||||||
|
append(fabric)
|
||||||
|
append(fabricVersionDropdown)
|
||||||
|
})
|
||||||
|
|
||||||
|
onOpen {
|
||||||
|
if (lastGameVersion == null || lastGameVersion != gameVersion) {
|
||||||
|
versions = FabricMetaApi.getLoaderVersions(gameVersion!!).toTypedArray()
|
||||||
|
def = versions.withIndex().firstOrNull { it.value.loader.stable }?.index ?: 0
|
||||||
|
fabricVersionDropdown.updateOptions(versions, def)
|
||||||
|
lastGameVersion = gameVersion
|
||||||
|
none.active = true
|
||||||
|
fabric.active = false
|
||||||
|
useFabric = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versions.isEmpty()) {
|
||||||
|
none.active = true
|
||||||
|
fabric.active = false
|
||||||
|
useFabric = false
|
||||||
|
fabric.sensitive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setComplete(true)
|
||||||
|
}
|
||||||
|
page("Name", AssistantPageType.CONTENT) {
|
||||||
|
append(Label(I18n["instance.settings.general.name.placeholder"]))
|
||||||
|
val entry = KEntry(name)
|
||||||
|
entry.placeholderText = I18n["instance.settings.general.name.placeholder"]
|
||||||
|
entry.valign
|
||||||
|
entry.onChange { name = InstanceNameTool.getNextValid(it) }
|
||||||
|
append(entry)
|
||||||
|
onOpen {
|
||||||
|
name = InstanceNameTool.getDefaultName(gameVersion!!.id, useFabric)
|
||||||
|
entry.text = name
|
||||||
|
}
|
||||||
|
|
||||||
|
setComplete(true)
|
||||||
|
}
|
||||||
|
page("Creating", AssistantPageType.PROGRESS) {
|
||||||
|
append(Label("Creating Instance"))
|
||||||
|
val progress = ProgressBar()
|
||||||
|
append(progress)
|
||||||
|
val stage = Label("")
|
||||||
|
append(stage)
|
||||||
|
onOpen {
|
||||||
|
commit()
|
||||||
|
val pState = Steps.createProcessState()
|
||||||
|
val state = SetupStepInfo(
|
||||||
|
McApi.getVersionInfo(gameVersion),
|
||||||
|
if (useFabric) LoaderInfo(fabricVersion!!.loader) else LoaderInfo.NONE,
|
||||||
|
name,
|
||||||
|
pState
|
||||||
|
)
|
||||||
|
var finished = false
|
||||||
|
var cachedState: ProcessStateWatcherDialog.State? = null
|
||||||
|
addTickCallback { widget, _ ->
|
||||||
|
if (finished) return@addTickCallback GLib.SOURCE_REMOVE
|
||||||
|
val nc = ProcessStateWatcherDialog.State(pState)
|
||||||
|
if (nc != cachedState) {
|
||||||
|
cachedState = nc
|
||||||
|
stage.markup = cachedState!!.msg
|
||||||
|
progress.fraction = cachedState!!.progress.coerceAtMost(1f).toDouble()
|
||||||
|
widget.queueDraw()
|
||||||
|
}
|
||||||
|
GLib.SOURCE_CONTINUE
|
||||||
|
}
|
||||||
|
onClose { pState.cancel() }
|
||||||
|
onCancel { pState.cancel() }
|
||||||
|
pState.updateStep("Starting install process")
|
||||||
|
backgroundTask {
|
||||||
|
try {
|
||||||
|
for (step in Steps.STEPS) {
|
||||||
|
if (state.isCancelled) {
|
||||||
|
state.tryRemoveInstance()
|
||||||
|
return@backgroundTask
|
||||||
|
}
|
||||||
|
pState.incrementStep(step.name)
|
||||||
|
step.execute(state)
|
||||||
|
}
|
||||||
|
state.clearSetupLock()
|
||||||
|
InstanceList.read(MetaHolder.INSTANCE_DIR.resolve(state.name)).mds.start()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
pState.cancel()
|
||||||
|
Log.error("Could not create instance")
|
||||||
|
failureMessage = StringFormatter.toString(e)
|
||||||
|
isFailure = true
|
||||||
|
state.tryRemoveInstance()
|
||||||
|
} finally {
|
||||||
|
finished = true
|
||||||
|
schedule { setComplete(true) }
|
||||||
|
schedule { nextPage() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page("Done", AssistantPageType.SUMMARY) {
|
||||||
|
val status = Label("")
|
||||||
|
onOpen {
|
||||||
|
if (isFailure) {
|
||||||
|
status.markup = "Something went wrong while creating the instance.\n\n$failureMessage"
|
||||||
|
} else {
|
||||||
|
status.markup = "The instance was successfully created. You can now launch it using the main menu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setComplete(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user