Compare commits
144 Commits
Author | SHA1 | Date |
---|---|---|
Johannes Frohnmeyer | aede030a7f | |
Johannes Frohnmeyer | 3e70e9e4e1 | |
Johannes Frohnmeyer | 4629d5f68e | |
Johannes Frohnmeyer | 7dd1d4dee2 | |
Johannes Frohnmeyer | f4ed8a4bcb | |
Johannes Frohnmeyer | af84674c26 | |
Johannes Frohnmeyer | 4aa1f24461 | |
Johannes Frohnmeyer | 9fd5d870cf | |
Johannes Frohnmeyer | e61df73dd3 | |
Johannes Frohnmeyer | c94c8b59af | |
Johannes Frohnmeyer | 475717b6b4 | |
Johannes Frohnmeyer | 5cc650921b | |
Johannes Frohnmeyer | 5b79987dcf | |
Johannes Frohnmeyer | 502026bb22 | |
Johannes Frohnmeyer | 3746e30ec7 | |
Johannes Frohnmeyer | ad033711f9 | |
Johannes Frohnmeyer | f1f2e95dd2 | |
Johannes Frohnmeyer | 9e1c20737d | |
Johannes Frohnmeyer | 04d8121ca2 | |
Johannes Frohnmeyer | f4157bae09 | |
Johannes Frohnmeyer | c7eabc37d3 | |
Johannes Frohnmeyer | 0bd675fc7c | |
Johannes Frohnmeyer | bcd4e34f7a | |
Johannes Frohnmeyer | 7a7d009e29 | |
Johannes Frohnmeyer | e14294fdd6 | |
Johannes Frohnmeyer | 98cc37405a | |
Johannes Frohnmeyer | b1b82c423a | |
Johannes Frohnmeyer | 32a01547c0 | |
Johannes Frohnmeyer | e19d7cfb3b | |
Johannes Frohnmeyer | d7ddde6c4d | |
Johannes Frohnmeyer | f091cb7a2b | |
Johannes Frohnmeyer | 5faa505235 | |
Johannes Frohnmeyer | 8aa0555a2a | |
Johannes Frohnmeyer | a7a135c598 | |
Johannes Frohnmeyer | 7f168ded43 | |
Johannes Frohnmeyer | cbba1ab70a | |
Johannes Frohnmeyer | ec67a80389 | |
Johannes Frohnmeyer | 1759e5c3de | |
Johannes Frohnmeyer | b8f30247ea | |
Johannes Frohnmeyer | 7805400e43 | |
Johannes Frohnmeyer | 55b9e5986f | |
Johannes Frohnmeyer | 7cefa88dcb | |
Johannes Frohnmeyer | 8f46e4887f | |
Johannes Frohnmeyer | 1be0d68a56 | |
Johannes Frohnmeyer | e9f8af5617 | |
Johannes Frohnmeyer | 9d287c6521 | |
Johannes Frohnmeyer | 7c4461a275 | |
Johannes Frohnmeyer | d8bd25b438 | |
Johannes Frohnmeyer | 218e12714d | |
Johannes Frohnmeyer | 3d73879bed | |
Johannes Frohnmeyer | d1c6b746f0 | |
Johannes Frohnmeyer | 71e784fdff | |
Johannes Frohnmeyer | 1db7bcde18 | |
Johannes Frohnmeyer | 8f056468a7 | |
Johannes Frohnmeyer | 25874c40f7 | |
Johannes Frohnmeyer | 3aaf7e3652 | |
Johannes Frohnmeyer | 17e89ba829 | |
Johannes Frohnmeyer | a89f51aa5d | |
Johannes Frohnmeyer | 441a9b26b2 | |
Johannes Frohnmeyer | 50e9db1fcc | |
Johannes Frohnmeyer | 6151f0e71e | |
Johannes Frohnmeyer | 8d45fdff84 | |
Johannes Frohnmeyer | 2cb852c5cb | |
Johannes Frohnmeyer | fc256a1376 | |
Johannes Frohnmeyer | 1353de777e | |
Johannes Frohnmeyer | 3f273bf6f8 | |
Johannes Frohnmeyer | 7a80132ab0 | |
Johannes Frohnmeyer | c027885364 | |
Johannes Frohnmeyer | 4967410f51 | |
Johannes Frohnmeyer | 14a23fdfed | |
Johannes Frohnmeyer | 7284193981 | |
Johannes Frohnmeyer | eb9601d6cf | |
Johannes Frohnmeyer | 5c9ce78ebf | |
Johannes Frohnmeyer | fe0c23b97b | |
Johannes Frohnmeyer | 94dbaadaf1 | |
Johannes Frohnmeyer | cd3c8a1852 | |
Johannes Frohnmeyer | 18c4953b36 | |
Johannes Frohnmeyer | 0cb3df331a | |
Johannes Frohnmeyer | 08bb13f994 | |
Johannes Frohnmeyer | a93f2c8411 | |
Johannes Frohnmeyer | e15ef8c485 | |
Johannes Frohnmeyer | 2ca25a7bee | |
Johannes Frohnmeyer | bdd86c7683 | |
Johannes Frohnmeyer | 442d462843 | |
Johannes Frohnmeyer | 37872e6c79 | |
Johannes Frohnmeyer | dff05af62f | |
Johannes Frohnmeyer | 05a18765c9 | |
Johannes Frohnmeyer | 36f462597a | |
Johannes Frohnmeyer | c52f1f3350 | |
Johannes Frohnmeyer | d4a016771f | |
Johannes Frohnmeyer | 8af7c214d2 | |
Johannes Frohnmeyer | 71faae3b9a | |
Johannes Frohnmeyer | 379f02c41c | |
Johannes Frohnmeyer | be8252ce58 | |
Johannes Frohnmeyer | fb56c9e922 | |
Johannes Frohnmeyer | 18810b255b | |
Johannes Frohnmeyer | 3370495207 | |
Johannes Frohnmeyer | b0326536c7 | |
Johannes Frohnmeyer | eb6c2538b5 | |
Johannes Frohnmeyer | 3563d7449b | |
Johannes Frohnmeyer | 7dee85292c | |
Johannes Frohnmeyer | d2b041ef59 | |
Johannes Frohnmeyer | 8eee28f353 | |
Johannes Frohnmeyer | e81c5765df | |
Johannes Frohnmeyer | 5dd3c4b0a2 | |
Johannes Frohnmeyer | 45fe59df83 | |
Johannes Frohnmeyer | 3de1bd0218 | |
Johannes Frohnmeyer | 22c27bb9ec | |
Johannes Frohnmeyer | 2c30e2e76a | |
Johannes Frohnmeyer | 0b65231175 | |
Johannes Frohnmeyer | 11a40975d9 | |
Johannes Frohnmeyer | 182d65a442 | |
Johannes Frohnmeyer | 2b1a8b86b2 | |
Johannes Frohnmeyer | ee72dc9a9c | |
Johannes Frohnmeyer | 705f4182aa | |
Johannes Frohnmeyer | 04b59ebac6 | |
Johannes Frohnmeyer | 958fb90822 | |
Johannes Frohnmeyer | 9bf6446082 | |
Johannes Frohnmeyer | ab5304dcd9 | |
Johannes Frohnmeyer | fcf729e65a | |
Johannes Frohnmeyer | 94c9e46f8b | |
Johannes Frohnmeyer | 109402731d | |
Johannes Frohnmeyer | 915c8cea6c | |
Johannes Frohnmeyer | 19a5aa3d9f | |
Johannes Frohnmeyer | 3eace3f6b8 | |
Johannes Frohnmeyer | 7ce6a764ec | |
Johannes Frohnmeyer | 841fa6b3d4 | |
Johannes Frohnmeyer | b7ca994fa8 | |
Johannes Frohnmeyer | 36063054c0 | |
Johannes Frohnmeyer | 9937b7c258 | |
Johannes Frohnmeyer | a31a1abc6c | |
Johannes Frohnmeyer | 0ad54e978e | |
Johannes Frohnmeyer | ff7dddbd35 | |
Johannes Frohnmeyer | 7e42e6b02f | |
Johannes Frohnmeyer | 5c6266634a | |
Johannes Frohnmeyer | 390f6bc59b | |
Johannes Frohnmeyer | ef986fab05 | |
Johannes Frohnmeyer | ab81759d4d | |
Johannes Frohnmeyer | b87889f5b4 | |
Johannes Frohnmeyer | 62dd7da114 | |
Johannes Frohnmeyer | e3b3e50bd2 | |
Johannes Frohnmeyer | b0a6146d49 | |
Johannes Frohnmeyer | eb3fb83673 | |
Johannes Frohnmeyer | 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'
|
|
@ -1,3 +1,3 @@
|
|||
[submodule "packaging/arch-linux"]
|
||||
path = packaging/arch-linux
|
||||
url = ssh://aur@aur.archlinux.org/inceptum-git.git
|
||||
url = https://aur.archlinux.org/inceptum-git.git
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
#link https://pages.frohnmeyer-wds.de/scripts/docs.yml
|
||||
#include https://pages.frohnmeyer-wds.de/scripts/clone.yml
|
||||
|
||||
pipeline:
|
||||
export_metadata:
|
||||
image: gradle:jdk21-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:jdk21-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:jdk21-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:jdk21-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
|
||||
Copyright (C) 2021 JFronny
|
||||
Copyright (C) 2021-2023 JFronny
|
||||
|
||||
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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
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
|
||||
|
||||
|
@ -14,7 +14,9 @@ Inceptum utilizes code/libraries/assets from:
|
|||
- [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
|
||||
- [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
|
||||
- [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
|
||||
- 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"
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://gitlab.com/jfmods/Inceptum"
|
||||
git-repository-icon = "fa-gitlab"
|
||||
edit-url-template = "https://gitlab.com/jfmods/Inceptum/edit/master/{path}"
|
||||
site-url = "https://jfmods.gitlab.io/inceptum"
|
||||
git-repository-url = "https://git.frohnmeyer-wds.de/JfMods/Inceptum"
|
||||
git-repository-icon = "fa-git-alt"
|
||||
edit-url-template = "https://git.frohnmeyer-wds.de/JfMods/Inceptum/_edit/master/{path}"
|
||||
site-url = "https://pages.frohnmeyer-wds.de/JfMods/Inceptum/"
|
||||
|
|
|
@ -1,49 +1,31 @@
|
|||
import org.ajoberstar.grgit.Grgit
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
import io.gitlab.jfronny.scripts.*
|
||||
|
||||
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 {
|
||||
version = currentVer + if (project.hasProperty("release")) "" else "-$timestamp"
|
||||
version = rootProject.version
|
||||
group = "io.gitlab.jfronny.inceptum"
|
||||
}
|
||||
|
||||
println("Using Inceptum Build Script $version")
|
||||
// common
|
||||
val jfCommonsVersion by extra(libs.versions.jf.commons.get())
|
||||
val gsonCompileVersion by extra(libs.versions.gson.compile.get())
|
||||
val jbAnnotationsVersion by extra(libs.versions.annotations.get())
|
||||
// launcher-imgui
|
||||
val lwjglVersion by extra(libs.versions.lwjgl.get())
|
||||
val imguiVersion by extra(libs.versions.imgui.get())
|
||||
// launcher-gtk
|
||||
val javagiVersion by extra(libs.versions.javagi.get())
|
||||
|
||||
val lwjglVersion by extra("3.3.1")
|
||||
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 flavorProp: String by extra(prop("flavor", "custom"))
|
||||
if (!setOf("custom", "maven", "fat", "windows", "linux", "macos").contains(flavorProp)) throw IllegalStateException("Unsupported flavor: $flavorProp")
|
||||
val flavor: String by extra(if (flavorProp != "custom") flavorProp else OS.TYPE.codename)
|
||||
val isPublic by extra(project.hasProperty("public"))
|
||||
val isRelease by extra(project.hasProperty("release"))
|
||||
|
||||
val buildTime by extra(System.currentTimeMillis())
|
||||
val wrapperVersion by extra(1)
|
||||
|
||||
tasks.register("exportMetadata") {
|
||||
|
@ -53,12 +35,13 @@ tasks.register("exportMetadata") {
|
|||
{
|
||||
"wrapperVersion": $wrapperVersion,
|
||||
"version": "$version",
|
||||
"buildTime": $buildTime,
|
||||
"isPublic": $isPublic,
|
||||
"isRelease": $isRelease,
|
||||
"jvm": ${project(":common").extra["javaVersion"]},
|
||||
"repositories": [
|
||||
"https://repo.maven.apache.org/maven2/",
|
||||
"https://gitlab.com/api/v4/projects/35745143/packages/maven"
|
||||
"https://maven.frohnmeyer-wds.de/artifacts/"
|
||||
],
|
||||
"natives": {
|
||||
"windows": [
|
||||
|
|
|
@ -4,9 +4,12 @@ plugins {
|
|||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven("https://maven.frohnmeyer-wds.de/artifacts")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2")
|
||||
implementation("de.undercouch:gradle-download-task:5.1.2")
|
||||
implementation(libs.plugin.shadow)
|
||||
implementation(libs.plugin.download)
|
||||
implementation(libs.plugin.jf.convention)
|
||||
implementation(libs.plugin.jlink)
|
||||
}
|
|
@ -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 java.io.FileOutputStream
|
||||
|
||||
plugins {
|
||||
application
|
||||
id("inceptum.java-conventions")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
id("de.undercouch.download")
|
||||
id("inceptum.java")
|
||||
com.github.johnrengelman.shadow
|
||||
de.undercouch.download
|
||||
}
|
||||
|
||||
abstract class FileOutput : DefaultTask() {
|
||||
|
@ -18,14 +18,14 @@ val bootstrapArch = "i686"
|
|||
|
||||
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")
|
||||
dest(project.buildDir)
|
||||
dest(project.layout.buildDirectory)
|
||||
}
|
||||
|
||||
val nativeExe by tasks.registering(FileOutput::class) {
|
||||
dependsOn(downloadBootstrap)
|
||||
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 }
|
||||
|
||||
doFirst {
|
||||
|
@ -43,4 +43,8 @@ val nativeExe by tasks.registering(FileOutput::class) {
|
|||
|
||||
if (rootProject.extra["flavor"] == "windows") {
|
||||
tasks.build.get().dependsOn(nativeExe)
|
||||
}
|
||||
|
||||
tasks.runShadow {
|
||||
workingDir = rootProject.projectDir
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
plugins {
|
||||
application
|
||||
id("inceptum.java-conventions")
|
||||
id("inceptum.java")
|
||||
}
|
||||
|
||||
publishing {
|
||||
|
@ -9,4 +9,6 @@ publishing {
|
|||
from(components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.run.get().workingDir = rootProject.projectDir
|
|
@ -0,0 +1,14 @@
|
|||
plugins {
|
||||
id("inceptum.library")
|
||||
}
|
||||
|
||||
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
dependencies {
|
||||
api(libs.findLibrary("gson-compile-core").orElseThrow())
|
||||
compileOnly(libs.findLibrary("gson-compile-annotations").orElseThrow())
|
||||
annotationProcessor(libs.findLibrary("gson-compile-processor").orElseThrow())
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.compilerArgs.add("-AgsonCompileNoReflect")
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
id("inceptum.java-conventions")
|
||||
id("inceptum.java")
|
||||
}
|
||||
|
||||
publishing {
|
|
@ -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 {
|
||||
id("inceptum.library-conventions")
|
||||
inceptum.library
|
||||
jf.codegen
|
||||
inceptum.`gson-compile`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api("io.gitlab.jfronny:commons:${rootProject.extra["jfCommonsVersion"]}")
|
||||
api("io.gitlab.jfronny:commons-gson:${rootProject.extra["jfCommonsVersion"]}")
|
||||
api(libs.bundles.commons)
|
||||
}
|
||||
|
||||
val javaVersion by extra(project.java.targetCompatibility)
|
||||
|
||||
projectDir.resolve("src/main/java/io/gitlab/jfronny/inceptum/common/BuildMetadata.java").writeText(
|
||||
"""
|
||||
package io.gitlab.jfronny.inceptum.common;
|
||||
sourceSets {
|
||||
main {
|
||||
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;
|
||||
|
||||
public class BuildMetadata {
|
||||
public static final ComparableVersion VERSION = new ComparableVersion("$version");
|
||||
public static final boolean IS_PUBLIC = ${rootProject.extra["isPublic"]};
|
||||
public static final boolean IS_RELEASE = ${rootProject.extra["isRelease"]};
|
||||
public static final int VM_VERSION = $javaVersion;
|
||||
public static final int WRAPPER_VERSION = ${rootProject.extra["wrapperVersion"]};
|
||||
field("VERSION", versionS, *modifiers)
|
||||
field("BUILD_TIME", rootProject.extra["buildTime"] as Long, *modifiers)
|
||||
field("IS_PUBLIC", rootProject.extra["isPublic"] as Boolean, *modifiers)
|
||||
field("IS_RELEASE", rootProject.extra["isRelease"] as Boolean, *modifiers)
|
||||
field("VM_VERSION", javaVersion.majorVersion.toInt(), *modifiers)
|
||||
field("WRAPPER_VERSION", rootProject.extra["wrapperVersion"] as Int, *modifiers)
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
import io.gitlab.jfronny.gson.stream.JsonReader;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
|
||||
public class GsonPreset {
|
||||
public static class Config {
|
||||
public static void configure(JsonReader reader) {
|
||||
reader.setSerializeSpecialFloatingPointValues(true);
|
||||
reader.setLenient(true);
|
||||
}
|
||||
|
||||
public static void configure(JsonWriter writer) {
|
||||
writer.setSerializeNulls(true);
|
||||
writer.setSerializeSpecialFloatingPointValues(true);
|
||||
writer.setLenient(true);
|
||||
writer.setIndent(" ");
|
||||
writer.setOmitQuotes(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Api {
|
||||
public static void configure(JsonReader reader) {
|
||||
reader.setSerializeSpecialFloatingPointValues(true);
|
||||
reader.setLenient(true);
|
||||
}
|
||||
|
||||
public static void configure(JsonWriter writer) {
|
||||
writer.setSerializeNulls(false);
|
||||
writer.setSerializeSpecialFloatingPointValues(true);
|
||||
writer.setLenient(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +1,30 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
||||
import io.gitlab.jfronny.gson.stream.*;
|
||||
import io.gitlab.jfronny.gson.compile.annotations.GComment;
|
||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
||||
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.Path;
|
||||
|
||||
@GSerializable(configure = GsonPreset.Config.class, isStatic = true)
|
||||
public class InceptumConfig {
|
||||
@GComment("Whether to show snapshots in the version selector for new instances")
|
||||
public static boolean snapshots = false;
|
||||
@GComment("Whether to launch the ImGUI in dark mode\nConfigurable in Settings->Dark Theme")
|
||||
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;
|
||||
@GComment("The currently selected account\nUsed to launch the game")
|
||||
public static String lastAccount = null;
|
||||
@GComment("The last name used for an offline session")
|
||||
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;
|
||||
@GComment("The author name to add to packs where the metadata format requires specifying one")
|
||||
public static String authorName = "Inceptum";
|
||||
|
||||
public static void load() throws IOException {
|
||||
|
@ -25,81 +35,20 @@ public class InceptumConfig {
|
|||
Path json = MetaHolder.BASE_PATH.resolve("inceptum.json");
|
||||
if (Files.exists(gLaunch2)) {
|
||||
Files.move(gLaunch2, MetaHolder.CONFIG_PATH);
|
||||
} if (Files.exists(json)) {
|
||||
} else if (Files.exists(json)) {
|
||||
Files.move(json, MetaHolder.CONFIG_PATH);
|
||||
} else {
|
||||
saveConfig();
|
||||
}
|
||||
}
|
||||
try (Reader reader = Files.newBufferedReader(MetaHolder.CONFIG_PATH);
|
||||
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();
|
||||
}
|
||||
GC_InceptumConfig.read(MetaHolder.CONFIG_PATH);
|
||||
}
|
||||
|
||||
public static void saveConfig() {
|
||||
try (Writer writer = Files.newBufferedWriter(MetaHolder.CONFIG_PATH);
|
||||
JsonWriter jw = GsonHolder.getGson().newJsonWriter(writer)) {
|
||||
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();
|
||||
try {
|
||||
GC_InceptumConfig.write(MetaHolder.CONFIG_PATH);
|
||||
} catch (IOException 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,19 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
import io.gitlab.jfronny.commons.HttpUtils;
|
||||
import io.gitlab.jfronny.commons.log.Logger;
|
||||
import io.gitlab.jfronny.commons.log.StdoutLogger;
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
||||
import io.gitlab.jfronny.commons.http.client.HttpClient;
|
||||
import io.gitlab.jfronny.commons.logging.Logger;
|
||||
import io.gitlab.jfronny.commons.logging.StdoutLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class InceptumEnvironmentInitializer {
|
||||
public static void initialize() throws IOException {
|
||||
Logger.registerFactory(InceptumEnvironmentInitializer::defaultFactory);
|
||||
HttpUtils.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
|
||||
GsonHolder.register();
|
||||
HttpClient.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
|
||||
InceptumConfig.load();
|
||||
}
|
||||
|
||||
public static Logger defaultFactory(String name) {
|
||||
return new StdoutLogger(name, true, true, true);
|
||||
return StdoutLogger.fancy(name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public class MetaHolder {
|
|||
case WINDOWS -> getPath(System.getenv("APPDATA"));
|
||||
case MAC_OS -> getPath(System.getProperty("user.home")).resolve("Library").resolve("Application Support");
|
||||
case LINUX -> {
|
||||
String s = System.getenv().get("XDG_CONFIG_HOME");
|
||||
String s = System.getenv("XDG_CONFIG_HOME");
|
||||
if (s == null)
|
||||
yield getPath(System.getProperty("user.home")).resolve(".config");
|
||||
else
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
import io.gitlab.jfronny.commons.HashUtils;
|
||||
import io.gitlab.jfronny.commons.HttpUtils;
|
||||
import io.gitlab.jfronny.commons.cache.FileBackedOperationResultCache;
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
||||
import io.gitlab.jfronny.commons.http.client.HttpClient;
|
||||
import io.gitlab.jfronny.commons.io.HashUtils;
|
||||
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
|
||||
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -17,10 +15,10 @@ import java.nio.file.Path;
|
|||
import java.util.Map;
|
||||
|
||||
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 {
|
||||
try (InputStream is = HttpUtils.get(url).sendInputStream()) {
|
||||
try (InputStream is = HttpClient.get(url).sendInputStream()) {
|
||||
return is.readAllBytes();
|
||||
}
|
||||
}
|
||||
|
@ -32,38 +30,30 @@ public class Net {
|
|||
return buf;
|
||||
}
|
||||
|
||||
public static <T> T downloadObject(String url, Class<T> type) throws IOException {
|
||||
return downloadObject(url, type, true);
|
||||
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func) throws IOException {
|
||||
return downloadObject(url, func, true);
|
||||
}
|
||||
|
||||
public static <T> T downloadObject(String url, Class<T> type, boolean cache) throws IOException {
|
||||
return downloadObject(url, () -> HttpUtils.get(url).sendString(), type, cache);
|
||||
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
||||
return downloadObject(url, () -> HttpClient.get(url).sendString(), func, cache);
|
||||
}
|
||||
|
||||
public static <T> T downloadObject(String url, Type type) throws IOException {
|
||||
return downloadObject(url, type, true);
|
||||
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, String apiKey) throws IOException {
|
||||
return downloadObject(url, () -> downloadStringAuthenticated(url, apiKey), func, true);
|
||||
}
|
||||
|
||||
public static <T> T downloadObject(String url, Type type, String apiKey) throws IOException {
|
||||
return downloadObject(url, () -> HttpUtils.get(url).header("x-api-key", apiKey).sendString(), type, true);
|
||||
public static <T> T downloadObject(String url, String sha1, ThrowingFunction<String, T, IOException> func) throws IOException {
|
||||
return downloadObject(url, sha1, func, true);
|
||||
}
|
||||
|
||||
public static <T> T downloadObject(String url, Type type, boolean cache) throws IOException {
|
||||
return downloadObject(url, () -> HttpUtils.get(url).sendString(), type, cache);
|
||||
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 downloadObject(String url, String sha1, Class<T> type) throws IOException {
|
||||
return downloadObject(url, sha1, type, true);
|
||||
}
|
||||
|
||||
public static <T> T downloadObject(String url, String sha1, Class<T> type, boolean cache) throws IOException {
|
||||
return downloadObject(url, () -> downloadString(url, sha1), type, cache);
|
||||
}
|
||||
|
||||
private static <T> T downloadObject(String url, ThrowingSupplier<String, Exception> sourceString, Type type, boolean cache) throws IOException {
|
||||
private static <T> T downloadObject(String url, ThrowingSupplier<String, Exception> sourceString, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
||||
try {
|
||||
ThrowingSupplier<T, Exception> builder = () -> GsonHolder.getGson().fromJson(sourceString.get(), type);
|
||||
return cache ? OBJECT_CACHE.get(HashUtils.sha1(url.getBytes(StandardCharsets.UTF_8)), builder, type) : builder.get();
|
||||
ThrowingSupplier<T, Exception> builder = () -> func.apply(sourceString.get());
|
||||
return cache ? OBJECT_CACHE.get(HashUtils.sha1(url.getBytes(StandardCharsets.UTF_8)), sourceString, func) : builder.get();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Could not download object and no cache exists", e);
|
||||
}
|
||||
|
@ -85,13 +75,17 @@ public class Net {
|
|||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
|
||||
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,14 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
import io.gitlab.jfronny.commons.ComparableVersion;
|
||||
import io.gitlab.jfronny.commons.OSUtils;
|
||||
import io.gitlab.jfronny.commons.io.JFiles;
|
||||
import io.gitlab.jfronny.inceptum.common.api.GitlabApi;
|
||||
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.maven.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -23,9 +18,12 @@ import java.util.stream.Collectors;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
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() {
|
||||
return Updater.check(InceptumConfig.channel, BuildMetadata.VERSION, channel -> {
|
||||
public static UpdateMetadata getUpdate(boolean versionCompare, boolean checkEnv) throws UpdateCheckException {
|
||||
return Updater.check(InceptumConfig.channel, versionCompare, checkEnv, channel -> {
|
||||
Utils.LOGGER.error("No stable version was found, switching to experimental channel");
|
||||
InceptumConfig.channel = channel;
|
||||
InceptumConfig.saveConfig();
|
||||
|
@ -33,36 +31,29 @@ public class Updater {
|
|||
}
|
||||
|
||||
public static void update(UpdateMetadata source, boolean relaunch) throws IOException, URISyntaxException {
|
||||
if (Runtime.version().feature() < source.jvm) {
|
||||
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());
|
||||
|
||||
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();
|
||||
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);
|
||||
DependencyNode node = downloadLibrary(source.repositories(), "io.gitlab.jfronny.inceptum:launcher-dist:" + source.version(), config.libraries());
|
||||
Utils.LOGGER.info("Downloaded Dependencies:\n" + node);
|
||||
|
||||
List<String> currentLibraries = new LinkedList<>(config.libraries);
|
||||
if (source.natives.containsKey(Utils.getCurrentFlavor())) {
|
||||
List<String> currentLibraries = new LinkedList<>(config.libraries());
|
||||
if (source.natives().containsKey(Utils.getCurrentFlavor())) {
|
||||
Set<String> natives = new LinkedHashSet<>();
|
||||
for (String lib : source.natives.get(Utils.getCurrentFlavor())){
|
||||
downloadLibrary(source.repositories, lib, natives);
|
||||
for (String lib : source.natives().get(Utils.getCurrentFlavor())) {
|
||||
downloadLibrary(source.repositories(), lib, 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.write(config, MetaHolder.WRAPPER_CONFIG_PATH);
|
||||
|
||||
if (relaunch) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
|
@ -71,7 +62,7 @@ public class Updater {
|
|||
"-cp",
|
||||
buildClasspath(currentLibraries.stream())
|
||||
.map(Path::toString)
|
||||
.collect(Collectors.joining("" + File.pathSeparatorChar))
|
||||
.collect(Collectors.joining(String.valueOf(File.pathSeparatorChar)))
|
||||
).inheritIO().start();
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not relaunch", e);
|
||||
|
@ -81,107 +72,81 @@ public class Updater {
|
|||
}
|
||||
|
||||
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<>();
|
||||
Set<String> libs = wrapperConfig.libraries;
|
||||
Set<String> libs = wrapperConfig.libraries();
|
||||
if (libs == null) libs = new LinkedHashSet<>();
|
||||
|
||||
boolean configChanged = false;
|
||||
|
||||
for (String lib : libs) {
|
||||
Path p = artifactToPath(lib);
|
||||
Path p = ArtifactMeta.parse(lib).getLocalPath();
|
||||
if (!Files.exists(p)) {
|
||||
configChanged = true;
|
||||
downloadLibrary(wrapperConfig.repositories, lib, libs);
|
||||
downloadLibrary(wrapperConfig.repositories(), lib, libs);
|
||||
}
|
||||
}
|
||||
for (String lib : natives) {
|
||||
Path p = artifactToPath(lib);
|
||||
Path p = ArtifactMeta.parse(lib).getLocalPath();
|
||||
if (!Files.exists(p)) {
|
||||
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.write(wrapperConfig, MetaHolder.WRAPPER_CONFIG_PATH);
|
||||
|
||||
return buildClasspath(Stream.concat(libs.stream(), natives.stream())).toList();
|
||||
}
|
||||
|
||||
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 {
|
||||
List<Exception> exceptions = new LinkedList<>();
|
||||
for (String repository : Stream.concat(Stream.of(GitlabApi.PROJECT_MAVEN), repositories.stream()).toList()) {
|
||||
List<FileNotFoundException> suppressed = new LinkedList<>();
|
||||
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;
|
||||
try {
|
||||
pom = MavenApi.getPom(repository, artifact);
|
||||
} catch (IOException | URISyntaxException | XMLStreamException | SAXException e) {
|
||||
exceptions.add(new Exception("Could not download artifact from " + repository, e));
|
||||
pom = MavenApi.getPom(repository, meta);
|
||||
} catch (FileNotFoundException notFound) {
|
||||
suppressed.add(notFound);
|
||||
continue;
|
||||
} catch (IOException | URISyntaxException | XMLStreamException | SAXException e) {
|
||||
throw new IOException("Could not download artifact " + meta.getMavenNotation() + " from " + repository, e);
|
||||
}
|
||||
Set<DependencyNode> dependencies = new LinkedHashSet<>();
|
||||
if (pom.dependencies != null) {
|
||||
for (MavenDependency dependency : pom.dependencies) {
|
||||
String mvnName = dependency.groupId + ":" + dependency.artifactId + ":" + dependency.version;
|
||||
if (pom.dependencies() != null) {
|
||||
for (MavenDependency dependency : pom.dependencies()) {
|
||||
String mvnName = dependency.groupId() + ":" + dependency.artifactId() + ":" + dependency.version();
|
||||
dependencies.add(downloadLibrary(repositories, mvnName, libraries));
|
||||
}
|
||||
}
|
||||
MavenApi.downloadLibrary(repository, artifact);
|
||||
MavenApi.downloadLibrary(repository, meta);
|
||||
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) + ")");
|
||||
for (Exception e : exceptions) {
|
||||
exception.addSuppressed(e);
|
||||
}
|
||||
throw exception;
|
||||
IOException e = new IOException("Could not find any repository containing the artifact " + artifact + " (searched: " + String.join(", ", repositories) + ")");
|
||||
for (FileNotFoundException ex : suppressed) e.addSuppressed(ex);
|
||||
throw e;
|
||||
}
|
||||
|
||||
private static Path artifactToPath(String artifact) {
|
||||
return MetaHolder.LIBRARIES_DIR.resolve(MavenApi.mavenNotationToJarPath(artifact)).toAbsolutePath();
|
||||
}
|
||||
|
||||
public static @Nullable UpdateMetadata check(UpdateChannel channel, ComparableVersion current, Consumer<UpdateChannel> channelInvalid) {
|
||||
public static @Nullable UpdateMetadata check(UpdateChannel channel, boolean versionCompare, boolean checkEnv, Consumer<UpdateChannel> channelInvalid) throws UpdateCheckException {
|
||||
try {
|
||||
int jvm = Runtime.version().feature();
|
||||
GitlabProject project = GitlabApi.getProject(GitlabApi.PROJECT_ID);
|
||||
UpdateMetadata experimental = null;
|
||||
UpdateMetadata experimental = Net.downloadObject(ARTIFACTS_URL + "version.json", json -> GC_UpdateMetadata.read(json));
|
||||
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 {
|
||||
UpdateMetadata update = Net.downloadObject(GitlabApi.PROJECTS + project.id + "/jobs/" + job.id + "/artifacts/version.json", UpdateMetadata.class);
|
||||
if (update.jvm > jvm) {
|
||||
Utils.LOGGER.error("A newer JVM is required to use the latest inceptum version. Please update!");
|
||||
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) {
|
||||
try {
|
||||
stable = Net.downloadObject(STABLE_URL + "version.json", json -> GC_UpdateMetadata.read(json));
|
||||
} catch (Throwable ignored) {}
|
||||
if (stable == null && channel == UpdateChannel.Stable) {
|
||||
channel = UpdateChannel.CI;
|
||||
channelInvalid.accept(channel);
|
||||
}
|
||||
|
@ -189,19 +154,39 @@ public class Updater {
|
|||
case CI -> experimental;
|
||||
case Stable -> stable;
|
||||
};
|
||||
Utils.LOGGER.info("Latest version is " + info.version + ", current is " + current);
|
||||
if (current.compareTo(info.version) >= 0) {
|
||||
Utils.LOGGER.info("Up-to-date");
|
||||
return null;
|
||||
if (checkEnv) {
|
||||
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");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not check for updates", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getShadowJarUrl(UpdateMetadata metadata) {
|
||||
return GitlabApi.PROJECT_MAVEN + "io/gitlab/jfronny/inceptum/Inceptum/" + metadata.version + "/Inceptum-" + metadata.version + "-" + Utils.getCurrentFlavor() + ".jar";
|
||||
public static String getShadowJarUrl(UpdateChannel channel) {
|
||||
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,7 +2,7 @@ package io.gitlab.jfronny.inceptum.common;
|
|||
|
||||
import io.gitlab.jfronny.commons.OSUtils;
|
||||
import io.gitlab.jfronny.commons.io.JFiles;
|
||||
import io.gitlab.jfronny.commons.log.Logger;
|
||||
import io.gitlab.jfronny.commons.logging.Logger;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
|
@ -17,6 +17,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
public class Utils {
|
||||
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 Logger LOGGER = Logger.forName("Inceptum");
|
||||
private static ClassLoader SYSTEM_LOADER = ClassLoader.getSystemClassLoader();
|
||||
|
@ -41,7 +42,7 @@ public class Utils {
|
|||
Desktop.getDesktop().open(file);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Utils.LOGGER.error("Error opening web browser!", e);
|
||||
Utils.LOGGER.error("Error opening file!", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,8 +59,9 @@ public class Utils {
|
|||
/**
|
||||
* 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
|
||||
*
|
||||
* @param separator The separator to join with
|
||||
* @param segments The strings to join
|
||||
* @param segments The strings to join
|
||||
* @return The joined string
|
||||
*/
|
||||
public static String join(String separator, String... segments) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
import io.gitlab.jfronny.commons.HttpUtils;
|
||||
import io.gitlab.jfronny.inceptum.common.*;
|
||||
import io.gitlab.jfronny.inceptum.common.model.maven.MavenDependency;
|
||||
import io.gitlab.jfronny.inceptum.common.model.maven.Pom;
|
||||
import io.gitlab.jfronny.commons.http.client.HttpClient;
|
||||
import io.gitlab.jfronny.inceptum.common.Net;
|
||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||
import io.gitlab.jfronny.inceptum.common.model.maven.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.w3c.dom.*;
|
||||
import org.xml.sax.SAXException;
|
||||
|
@ -28,125 +28,170 @@ public class MavenApi {
|
|||
}
|
||||
}
|
||||
|
||||
public static Path downloadLibrary(String repo, String artifact) throws IOException, URISyntaxException {
|
||||
String path = mavenNotationToJarPath(artifact);
|
||||
Path res = MetaHolder.LIBRARIES_DIR.resolve(path);
|
||||
Net.downloadFile(Utils.join("/", repo, path), res);
|
||||
public static Path downloadLibrary(String repo, ArtifactMeta meta) throws IOException, URISyntaxException {
|
||||
Path res = meta.getLocalPath();
|
||||
Net.downloadFile(Utils.join("/", repo, meta.getJarPath(true)), res);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static Pom getPom(String repo, String artifact) throws IOException, SAXException, URISyntaxException, XMLStreamException {
|
||||
try (InputStream is = HttpUtils.get(Utils.join("/", repo, mavenNotationToPomPath(artifact))).sendInputStream()) {
|
||||
public static Pom getPom(String repo, ArtifactMeta meta) throws IOException, SAXException, URISyntaxException, XMLStreamException {
|
||||
try (InputStream is = HttpClient.get(Utils.join("/", repo, meta.getPomPath())).sendInputStream()) {
|
||||
Document doc = FACTORY.parse(is);
|
||||
doc.getDocumentElement().normalize();
|
||||
Pom result = new Pom();
|
||||
if (!"project".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
|
||||
boolean hasModelVersion = false;
|
||||
boolean hasGroupId = false;
|
||||
boolean hasArtifactId = false;
|
||||
boolean hasVersion = false;
|
||||
for (Node node : iterable(doc.getDocumentElement().getChildNodes())) {
|
||||
String modelVersion = null;
|
||||
String groupId = null;
|
||||
String artifactId = null;
|
||||
String version = null;
|
||||
String packaging = null;
|
||||
List<MavenDependency> dependencies = null;
|
||||
String classifier = null;
|
||||
for (Node node : children(doc.getDocumentElement())) {
|
||||
switch (node.getNodeName()) {
|
||||
case "modelVersion" -> {
|
||||
hasModelVersion = true;
|
||||
result.modelVersion = node.getTextContent();
|
||||
}
|
||||
case "modelVersion" -> modelVersion = node.getTextContent();
|
||||
case "parent" -> {
|
||||
// Dirty hack to get slf4j working: simply assume the groupId and version of the parent is also the groupId of this
|
||||
if (!hasGroupId) {
|
||||
for (Node child : iterable(node.getChildNodes())) {
|
||||
if (groupId == null) {
|
||||
for (Node child : children(node)) {
|
||||
switch (child.getNodeName()) {
|
||||
case "groupId" -> {
|
||||
if (!hasGroupId) {
|
||||
hasGroupId = true;
|
||||
result.groupId = node.getTextContent();
|
||||
if (groupId == null) {
|
||||
groupId = child.getTextContent();
|
||||
}
|
||||
}
|
||||
case "version" -> {
|
||||
if (!hasVersion) {
|
||||
hasVersion = true;
|
||||
result.version = node.getTextContent();
|
||||
if (version == null) {
|
||||
version = child.getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "groupId" -> {
|
||||
hasGroupId = true;
|
||||
result.groupId = node.getTextContent();
|
||||
}
|
||||
case "groupId" -> groupId = node.getTextContent();
|
||||
case "artifactId" -> {
|
||||
hasArtifactId = true;
|
||||
result.artifactId = node.getTextContent();
|
||||
artifactId = node.getTextContent();
|
||||
}
|
||||
case "version" -> {
|
||||
hasVersion = true;
|
||||
result.version = node.getTextContent();
|
||||
version = node.getTextContent();
|
||||
}
|
||||
case "packaging" -> result.packaging = node.getTextContent();
|
||||
case "packaging" -> packaging = node.getTextContent();
|
||||
case "dependencies" -> {
|
||||
result.dependencies = new LinkedList<>();
|
||||
for (Node dep : iterable(node.getChildNodes())) {
|
||||
dependencies = new LinkedList<>();
|
||||
for (Node dep : children(node)) {
|
||||
MavenDependency resolved = parseDependency(dep);
|
||||
if (resolved != null) {
|
||||
result.dependencies.add(resolved);
|
||||
dependencies.add(resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
case "classifier" -> classifier = node.getTextContent();
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
if (!hasModelVersion) throw new IOException("Pom lacks modelVersion");
|
||||
if (!hasGroupId) throw new IOException("Pom lacks groupId");
|
||||
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
|
||||
if (!hasVersion) throw new IOException("Pom lacks version");
|
||||
return result;
|
||||
if (modelVersion == null) throw new IOException("Pom lacks modelVersion");
|
||||
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 Pom(modelVersion, groupId, artifactId, version, classifier, null, packaging, dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable MavenDependency parseDependency(Node doc) throws IOException {
|
||||
MavenDependency result = new MavenDependency();
|
||||
boolean hasGroupId = false;
|
||||
boolean hasArtifactId = false;
|
||||
boolean hasVersion = false;
|
||||
boolean hasScope = false;
|
||||
for (Node node : iterable(doc.getChildNodes())) {
|
||||
String groupId = null;
|
||||
String artifactId = null;
|
||||
String version = null;
|
||||
String scope = null;
|
||||
for (Node node : children(doc)) {
|
||||
switch (node.getNodeName()) {
|
||||
case "groupId" -> {
|
||||
hasGroupId = true;
|
||||
result.groupId = node.getTextContent();
|
||||
}
|
||||
case "artifactId" -> {
|
||||
hasArtifactId = true;
|
||||
result.artifactId = node.getTextContent();
|
||||
}
|
||||
case "version" -> {
|
||||
hasVersion = true;
|
||||
result.version = node.getTextContent();
|
||||
}
|
||||
case "groupId" -> groupId = node.getTextContent();
|
||||
case "artifactId" -> artifactId = node.getTextContent();
|
||||
case "version" -> version = node.getTextContent();
|
||||
case "scope" -> {
|
||||
hasScope = true;
|
||||
result.scope = node.getTextContent();
|
||||
if (!RUNTIME_SCOPES.contains(result.scope)) return null;
|
||||
scope = node.getTextContent();
|
||||
if (!RUNTIME_SCOPES.contains(scope)) return null;
|
||||
}
|
||||
case "optional" -> {
|
||||
if (node.getTextContent().equals("true")) return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasGroupId) throw new IOException("Pom lacks groupId");
|
||||
if (!hasArtifactId) throw new IOException("Pom lacks artifactId");
|
||||
if (!hasVersion) {
|
||||
if (result.groupId.equals("org.lwjgl")) {
|
||||
if (groupId == null) throw new IOException("Pom lacks groupId");
|
||||
if (artifactId == null) throw new IOException("Pom lacks artifactId");
|
||||
if (version == null) {
|
||||
if (groupId.equals("org.lwjgl")) {
|
||||
// Lwjgl uses a shared bom for versions which I don't want to support
|
||||
// The required modules are explicit dependencies of launcher-imgui anyway
|
||||
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");
|
||||
return result;
|
||||
if (scope == null) throw new IOException("Pom lacks scope");
|
||||
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) {
|
||||
|
@ -154,67 +199,4 @@ public class MavenApi {
|
|||
if (node.getNodeType() == Node.COMMENT_NODE) return true;
|
||||
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;
|
||||
|
||||
import io.gitlab.jfronny.commons.ComparableVersion;
|
||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
||||
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class UpdateMetadata {
|
||||
public Integer wrapperVersion;
|
||||
public ComparableVersion version;
|
||||
public Boolean isPublic;
|
||||
public Boolean isRelease;
|
||||
public Integer jvm;
|
||||
public Set<String> repositories;
|
||||
public Map<String, Set<String>> natives;
|
||||
@GSerializable(configure = GsonPreset.Api.class)
|
||||
public record UpdateMetadata(int wrapperVersion,
|
||||
String version,
|
||||
long buildTime,
|
||||
boolean isPublic,
|
||||
boolean isRelease,
|
||||
int jvm,
|
||||
Set<String> repositories,
|
||||
Map<String, Set<String>> natives) {
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
||||
|
||||
import java.util.*;
|
||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
||||
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||
|
||||
public class WrapperConfig {
|
||||
public Set<String> libraries;
|
||||
public Set<String> repositories;
|
||||
public Map<String, Set<String>> natives;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@GSerializable(configure = GsonPreset.Config.class)
|
||||
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;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
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);
|
||||
}
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
public record DependencyNode(String name, Set<DependencyNode> dependencies) {
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
@ -34,7 +13,7 @@ public class DependencyNode {
|
|||
|
||||
private void generateTree(StringBuilder sb, String prefix, String childrenPrefix) {
|
||||
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();
|
||||
if (it.hasNext()) {
|
||||
next.generateTree(sb, childrenPrefix + "├── ", childrenPrefix + "│ ");
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
package io.gitlab.jfronny.inceptum.common.model.maven;
|
||||
|
||||
public class MavenDependency {
|
||||
public String groupId;
|
||||
public String artifactId;
|
||||
public String version;
|
||||
public String scope;
|
||||
public record MavenDependency(String groupId, String artifactId, String version, String scope) {
|
||||
}
|
||||
|
|
|
@ -4,11 +4,12 @@ import org.jetbrains.annotations.Nullable;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
public class Pom {
|
||||
public String modelVersion;
|
||||
public String groupId;
|
||||
public String artifactId;
|
||||
public String version;
|
||||
@Nullable public String packaging;
|
||||
@Nullable public List<MavenDependency> dependencies;
|
||||
public record Pom(String modelVersion,
|
||||
String groupId,
|
||||
String artifactId,
|
||||
String version,
|
||||
String classifier,
|
||||
String snapshotVersion,
|
||||
@Nullable String packaging,
|
||||
@Nullable List<MavenDependency> dependencies) {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
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.gson;
|
||||
requires transitive io.gitlab.jfronny.commons.http.client;
|
||||
requires transitive io.gitlab.jfronny.commons.io;
|
||||
requires transitive io.gitlab.jfronny.commons.logging;
|
||||
requires transitive io.gitlab.jfronny.gson;
|
||||
requires transitive io.gitlab.jfronny.gson.compile.core;
|
||||
requires static org.jetbrains.annotations;
|
||||
requires static io.gitlab.jfronny.gson.compile.annotations;
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
# 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
|
||||
|
||||
## The inceptum wrapper
|
||||
|
||||
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.
|
||||
|
||||
## 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)
|
||||
|
||||
```json
|
||||
```json5
|
||||
{
|
||||
// Whether to show snapshots in the version selector for new instances
|
||||
"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
|
||||
|
||||
```json
|
||||
```json5
|
||||
{
|
||||
// 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")
|
||||
|
@ -74,7 +74,7 @@ Please note that all entries except for "version" are optional
|
|||
|
||||
## *.imod (Mod Metadata)
|
||||
|
||||
```json
|
||||
```json5
|
||||
{
|
||||
// Where the JAR file for this mod can be obtained
|
||||
"sources": [
|
||||
|
@ -121,6 +121,8 @@ Please note that all entries except for "version" are optional
|
|||
// A list of dependencies by their file names
|
||||
"dependencies": [
|
||||
"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:
|
||||
|
||||
- [Windows x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum-windows.jar?job=build_platform_jars)
|
||||
- [MacOS x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum-macos.jar?job=build_platform_jars) (untested)
|
||||
- [Linux x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/Inceptum-linux.jar?job=build_platform_jars)
|
||||
- [Windows x86_64](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/Inceptum-windows.jar)
|
||||
- [MacOS x86_64](https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/Inceptum-macos.jar) (untested)
|
||||
- [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)
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
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.
|
||||
Please be aware that using this WILL result in worse performance.
|
|
@ -0,0 +1,52 @@
|
|||
[versions]
|
||||
jf-commons = "1.5-SNAPSHOT"
|
||||
gson-compile = "1.4-SNAPSHOT"
|
||||
annotations = "24.0.1"
|
||||
lwjgl = "3.3.2"
|
||||
imgui = "1.86.10"
|
||||
javagi = "0.9.0"
|
||||
kotlin = "1.9.20"
|
||||
|
||||
[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-io = { module = "io.gitlab.jfronny:commons-io", version.ref = "jf-commons" }
|
||||
commons-logging = { module = "io.gitlab.jfronny:commons-logging", version.ref = "jf-commons" }
|
||||
commons-serialize-gson = { module = "io.gitlab.jfronny:commons-serialize-gson", version.ref = "jf-commons" }
|
||||
|
||||
gson-compile-core = { module = "io.gitlab.jfronny.gson:gson-compile-core", version.ref = "gson-compile" }
|
||||
gson-compile-annotations = { module = "io.gitlab.jfronny.gson:gson-compile-annotations", version.ref = "gson-compile" }
|
||||
gson-compile-processor = { module = "io.gitlab.jfronny.gson:gson-compile-processor", version.ref = "gson-compile" }
|
||||
|
||||
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-logging", "commons-serialize-gson"]
|
|
@ -1,5 +1,5 @@
|
|||
plugins {
|
||||
id("inceptum.application-conventions")
|
||||
inceptum.application
|
||||
}
|
||||
|
||||
application {
|
||||
|
@ -7,5 +7,5 @@ application {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":launcher"))
|
||||
implementation(projects.launcher)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package io.gitlab.jfronny.inceptum.cli;
|
||||
|
||||
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.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.nio.file.Files;
|
||||
|
@ -11,11 +12,7 @@ import java.nio.file.Path;
|
|||
import java.util.List;
|
||||
|
||||
public abstract class BaseInstanceCommand extends Command {
|
||||
public BaseInstanceCommand(String help, String usage, String... aliases) {
|
||||
super(help, mutateUsage(usage), aliases);
|
||||
}
|
||||
|
||||
public BaseInstanceCommand(String help, String usage, List<String> aliases, List<Command> subCommands) {
|
||||
protected BaseInstanceCommand(String help, String usage, List<String> aliases, List<Command> subCommands) {
|
||||
super(help, mutateUsage(usage), aliases, subCommands);
|
||||
}
|
||||
|
||||
|
@ -31,31 +28,34 @@ public abstract class BaseInstanceCommand extends Command {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(CommandArgs args) {
|
||||
protected void invoke(CommandArgs args) throws Exception {
|
||||
if (args.length == 0) {
|
||||
Utils.LOGGER.error("You must specify an instance to commit in");
|
||||
return;
|
||||
}
|
||||
Path instancePath = MetaHolder.INSTANCE_DIR.resolve(args.get(0));
|
||||
Path instanceMetaPath = instancePath.resolve("instance.json");
|
||||
if (!Files.exists(instanceMetaPath)) {
|
||||
Utils.LOGGER.error("Invalid instance: \"" + args.get(0) + "\"");
|
||||
return;
|
||||
}
|
||||
InstanceMeta meta;
|
||||
try {
|
||||
meta = JFiles.readObject(instanceMetaPath, InstanceMeta.class);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not read instance metadata", e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
invoke(args.subArgs(), instancePath, meta);
|
||||
} catch (Exception e) {
|
||||
Utils.LOGGER.error("Could not execute command", e);
|
||||
return;
|
||||
Instance instance;
|
||||
Path normalPath = Path.of(args.get(0));
|
||||
if (Files.exists(normalPath.resolve(Instance.CONFIG_NAME))) {
|
||||
instance = new Instance(normalPath, GC_InstanceMeta.read(normalPath.resolve(Instance.CONFIG_NAME)));
|
||||
} 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) + "\"");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
instance = InstanceList.read(instancePath);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not read instance metadata", 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 ListCommand(),
|
||||
new ModCommand(),
|
||||
new ImportCommand(),
|
||||
new ExportCommand(),
|
||||
new JvmStateCommand(),
|
||||
new BatchCommand()
|
||||
|
@ -46,6 +47,8 @@ public class CliMain {
|
|||
|
||||
try {
|
||||
command.invoke();
|
||||
} catch (Exception e) {
|
||||
Utils.LOGGER.error("Could not execute command", e);
|
||||
} finally {
|
||||
LauncherEnv.terminate();
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public class HelpBuilder {
|
|||
builder.append(", ");
|
||||
}
|
||||
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");
|
||||
for (String s : upper) {
|
||||
usagePrefix.append(" ").append(s);
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.cli.*;
|
||||
import io.gitlab.jfronny.inceptum.common.R;
|
||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.export.Exporters;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.exporter.Exporters;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||
import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -25,14 +22,11 @@ public class ExportCommand extends BaseInstanceCommand {
|
|||
}
|
||||
|
||||
@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 == 1) throw new IllegalAccessException("You must specify a version number");
|
||||
if (args.length != 2) throw new IllegalAccessException("Too many arguments");
|
||||
ProcessState state = new ProcessState();
|
||||
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));
|
||||
if (args.length > 2) throw new IllegalAccessException("Too many arguments");
|
||||
if (args.length > 1) instance.meta().instanceVersion = args.get(1);
|
||||
Exporters.CURSE_FORGE.generate(new ProcessState(), instance, Paths.get(args.get(0)));
|
||||
}
|
||||
|
||||
private static class MultiMCExportCommand extends BaseInstanceCommand {
|
||||
|
@ -41,13 +35,10 @@ public class ExportCommand extends BaseInstanceCommand {
|
|||
}
|
||||
|
||||
@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 != 1) throw new IllegalAccessException("Too many arguments");
|
||||
ProcessState state = new ProcessState();
|
||||
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");
|
||||
Exporters.MULTI_MC.generate(new ProcessState(), instance, Paths.get(args.get(0)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,14 +48,11 @@ public class ExportCommand extends BaseInstanceCommand {
|
|||
}
|
||||
|
||||
@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 == 1) throw new IllegalAccessException("You must specify a version number");
|
||||
if (args.length != 2) throw new IllegalAccessException("Too many arguments");
|
||||
ProcessState state = new ProcessState();
|
||||
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));
|
||||
if (args.length > 2) throw new IllegalAccessException("Too many arguments");
|
||||
if (args.length > 1) instance.meta().instanceVersion = args.get(1);
|
||||
Exporters.MODRINTH.generate(new ProcessState(), instance, Paths.get(args.get(0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||
|
||||
import io.gitlab.jfronny.commons.logging.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
|
||||
System.out.println("\t(cannot display components as not a URLClassLoader)");
|
||||
|
||||
if (loader.getParent() != null)
|
||||
dumpClasspath(loader.getParent());
|
||||
if (loader.getParent() != null) 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.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.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.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class LaunchCommand extends BaseInstanceCommand {
|
||||
private final boolean server;
|
||||
|
@ -19,7 +20,7 @@ public class LaunchCommand extends BaseInstanceCommand {
|
|||
public LaunchCommand() {
|
||||
super("Launches an instance of the game (client by default). Non-blocking (batch commands will continue if this is ran)",
|
||||
"[game arguments...]",
|
||||
List.of("run", "launch", "start"),
|
||||
List.of("run", "instance.launch", "start"),
|
||||
List.of(
|
||||
new LaunchCommand("Explicitly launch a client", "client", false, false),
|
||||
new LaunchCommand("Launch a server", "server", true, false,
|
||||
|
@ -37,29 +38,28 @@ public class LaunchCommand extends BaseInstanceCommand {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException, InstanceLauncher.LaunchException {
|
||||
if (InstanceLock.isSetupLocked(instancePath)) {
|
||||
protected void invoke(CommandArgs args, Instance instance) throws IOException, LaunchException {
|
||||
if (instance.isSetupLocked()) {
|
||||
Utils.LOGGER.error("This instance is still being set up");
|
||||
return;
|
||||
}
|
||||
if (InstanceLock.isRunningLocked(instancePath)) {
|
||||
if (instance.isRunningLocked()) {
|
||||
Utils.LOGGER.error("This instance is already running");
|
||||
return;
|
||||
}
|
||||
if (args.length > 1) {
|
||||
if (meta.arguments == null) meta.arguments = new InstanceMeta.Arguments();
|
||||
meta.arguments.client = meta.arguments.client == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.client);
|
||||
meta.arguments.server = meta.arguments.server == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.server);
|
||||
meta.arguments.jvm = meta.arguments.jvm == null ? new ArrayList<>() : new ArrayList<>(meta.arguments.jvm);
|
||||
meta.arguments.client.addAll(args.after(0));
|
||||
meta.arguments.server.addAll(args.after(0));
|
||||
InstanceMeta meta = instance.meta();
|
||||
meta.checkArguments();
|
||||
meta.arguments = meta.arguments
|
||||
.withClient(Stream.concat(meta.arguments.client().stream(), args.after(0).stream()).toList())
|
||||
.withServer(Stream.concat(meta.arguments.server().stream(), args.after(0).stream()).toList());
|
||||
}
|
||||
Steps.reDownload(instancePath, Steps.createProcessState());
|
||||
Steps.reDownload(instance, Steps.createProcessState());
|
||||
if (server) {
|
||||
InstanceLauncher.launch(instancePath, meta, InstanceLauncher.LaunchType.Server, restart, AccountManager.NULL_AUTH);
|
||||
InstanceLauncher.launch(instance, LaunchType.Server, restart, AccountManager.NULL_AUTH);
|
||||
} else {
|
||||
AccountManager.loadAccounts();
|
||||
InstanceLauncher.launchClient(instancePath, meta);
|
||||
InstanceLauncher.launchClient(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||
|
||||
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.CommandArgs;
|
||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.launcher.util.InstanceLock;
|
||||
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
||||
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.nio.file.Files;
|
||||
|
@ -23,29 +23,29 @@ public class ListCommand extends Command {
|
|||
List<Path> paths = JFiles.list(MetaHolder.INSTANCE_DIR);
|
||||
if (paths.isEmpty()) System.out.println("No instances are currently present");
|
||||
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)");
|
||||
continue;
|
||||
}
|
||||
System.out.println("- \"" + path.getFileName().toString() + "\"");
|
||||
if (InstanceLock.isSetupLocked(path)) {
|
||||
System.out.println(" Status: Setting up");
|
||||
continue;
|
||||
}
|
||||
InstanceMeta instance;
|
||||
Instance instance;
|
||||
try {
|
||||
instance = JFiles.readObject(path.resolve("instance.json"), InstanceMeta.class);
|
||||
instance = InstanceList.read(path);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error(" Could not load instance.json", e);
|
||||
continue;
|
||||
}
|
||||
System.out.println(" Status: " + (InstanceLock.isRunningLocked(path) ? "Running" : "Stopped"));
|
||||
System.out.println(" Version: " + instance.getMinecraftVersion());
|
||||
if (instance.isSetupLocked()) {
|
||||
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.java != null) System.out.println(" Custom Java: " + instance.java);
|
||||
if (instance.minMem != null || instance.maxMem != null)
|
||||
System.out.println(" Memory:" + (instance.minMem != null ? " Minimum: " + instance.minMem : "")
|
||||
+ (instance.maxMem != null ? " Maximum: " + instance.maxMem : ""));
|
||||
if (instance.meta().java != null) System.out.println(" Custom Java: " + instance.meta().java);
|
||||
if (instance.meta().minMem != null || instance.meta().maxMem != null)
|
||||
System.out.println(" Memory:" + (instance.meta().minMem != null ? " Minimum: " + instance.meta().minMem : "")
|
||||
+ (instance.meta().maxMem != null ? " Maximum: " + instance.meta().maxMem : ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,10 @@ import io.gitlab.jfronny.commons.io.JFiles;
|
|||
import io.gitlab.jfronny.commons.throwable.ThrowingBiFunction;
|
||||
import io.gitlab.jfronny.inceptum.cli.*;
|
||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.launcher.util.ModManager;
|
||||
import io.gitlab.jfronny.inceptum.launcher.util.ModPath;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.mds.IWModDescription;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Mod;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
|
||||
import io.gitlab.jfronny.inceptum.launcher.util.Unchecked;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
@ -47,16 +45,15 @@ public class ModCommand extends Command {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException {
|
||||
if (!meta.isFabric()) {
|
||||
protected void invoke(CommandArgs args, Instance instance) throws IOException {
|
||||
if (!instance.isFabric()) {
|
||||
System.err.println("This is not a fabric instance");
|
||||
return;
|
||||
}
|
||||
System.out.println("Scanning installed mods, this might take a while");
|
||||
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
|
||||
mds.runOnce((path, mod) -> {
|
||||
boolean hasSources = mod.mod().isPresent() && !mod.mod().get().sources.isEmpty();
|
||||
boolean updatable = hasSources && mod.mod().get().sources.values().stream().anyMatch(Optional::isPresent);
|
||||
instance.mds().runOnce((path, mod) -> {
|
||||
boolean hasSources = !mod.getMetadata().sources().isEmpty();
|
||||
boolean updatable = hasSources && mod.getMetadata().sources().values().stream().anyMatch(Optional::isPresent);
|
||||
if (filterUpdatable && !updatable) return;
|
||||
System.out.println("- " + path.getFileName().toString());
|
||||
System.out.println(" " + mod.getName());
|
||||
|
@ -65,11 +62,10 @@ public class ModCommand extends Command {
|
|||
}
|
||||
if (hasSources) {
|
||||
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(" Local: " + entry.getKey().getJarPath().toString());
|
||||
if (entry.getValue().isPresent())
|
||||
System.out.println(" Updatable to: " + entry.getValue().get().getVersion());
|
||||
if (entry.getValue().isPresent()) System.out.println(" Updatable to: " + entry.getValue().get().getVersion());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -91,8 +87,8 @@ public class ModCommand extends Command {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException {
|
||||
if (!meta.isFabric()) {
|
||||
protected void invoke(CommandArgs args, Instance instance) throws IOException {
|
||||
if (!instance.isFabric()) {
|
||||
Utils.LOGGER.error("This is not a fabric instance");
|
||||
return;
|
||||
}
|
||||
|
@ -102,22 +98,22 @@ public class ModCommand extends Command {
|
|||
}
|
||||
Set<Path> mods = new HashSet<>();
|
||||
for (String arg : args) {
|
||||
Path p = instancePath.resolve("mods").resolve(arg);
|
||||
if (!Files.exists(p)) p = instancePath.resolve("mods").resolve(arg + ".imod");
|
||||
Path p = instance.getModsDir().resolve(arg);
|
||||
if (!Files.exists(p)) p = instance.getModsDir().resolve(arg + ".imod");
|
||||
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;
|
||||
}
|
||||
mods.add(p);
|
||||
}
|
||||
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
|
||||
ModsDirScanner mds = instance.mds();
|
||||
if (!ignoreDependencies && !mds.isComplete()) {
|
||||
Utils.LOGGER.error("Scanning mods dir to search for dependencies. This might take a while");
|
||||
mds.runOnce((path, mod) -> System.out.println("Scanned " + path));
|
||||
}
|
||||
for (Path mod : mods) {
|
||||
try {
|
||||
ModManager.delete(mds.get(mod), instancePath.resolve("mods"), mds);
|
||||
mds.get(mod).delete();
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not delete " + mod, e);
|
||||
return;
|
||||
|
@ -152,50 +148,40 @@ public class ModCommand extends Command {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(CommandArgs args, Path instancePath, InstanceMeta meta) throws IOException {
|
||||
if (!meta.isFabric()) {
|
||||
protected void invoke(CommandArgs args, Instance instance) throws IOException {
|
||||
if (!instance.isFabric()) {
|
||||
throw new IOException("This is not a fabric instance");
|
||||
}
|
||||
if (args.length == 0) {
|
||||
throw new IllegalArgumentException("You must specify mods to remove");
|
||||
}
|
||||
Set<Path> mods = pathSupplier.apply(args, instancePath);
|
||||
ModsDirScanner mds = ModsDirScanner.get(instancePath.resolve("mods"), meta);
|
||||
Set<Path> mods = pathSupplier.apply(args, instance.path());
|
||||
ModsDirScanner mds = instance.mds();
|
||||
if (!mds.isComplete()) {
|
||||
Utils.LOGGER.error("Scanning mods dir to search for dependencies. This might take a while");
|
||||
mds.runOnce((path, mod) -> System.out.println("Scanned " + path));
|
||||
}
|
||||
for (Path mod : mods) {
|
||||
try {
|
||||
Utils.LOGGER.info("Updating " + mod);
|
||||
ModManager.delete(mds.get(mod), instancePath.resolve("mods"), mds);
|
||||
IWModDescription md = mds.get(mod);
|
||||
if (md.mod().isEmpty()) {
|
||||
throw new IOException("Could not load mod description");
|
||||
}
|
||||
boolean found = false;
|
||||
for (Map.Entry<ModSource, Optional<ModSource>> source : md.mod().get().sources.entrySet()) {
|
||||
Optional<ModSource> ms = source.getValue();
|
||||
if (ms.isPresent()) {
|
||||
try {
|
||||
Utils.LOGGER.info("Updating to " + ms.get().getVersion());
|
||||
Path imodPath = md.imod().isPresent() ? md.imod().get() : instancePath.resolve("mods").resolve(ms.get().getShortName() + ModPath.EXT_IMOD);
|
||||
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");
|
||||
found = true;
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Update failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) Utils.LOGGER.error("Could not find any update");
|
||||
Mod md = mds.get(mod);
|
||||
md.delete();
|
||||
md.getMetadata().sources().values().stream()
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.findFirst()
|
||||
.ifPresentOrElse(update -> {
|
||||
try {
|
||||
Utils.LOGGER.info("Updating " + mod + " to " + update.getVersion());
|
||||
md.update(update);
|
||||
Utils.LOGGER.info("Update completed");
|
||||
} catch (IOException e) {
|
||||
throw new Unchecked(e);
|
||||
}
|
||||
}, () -> Utils.LOGGER.error("Could not find any update for " + mod));
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Could not delete " + mod, e);
|
||||
} catch (Unchecked e) {
|
||||
throw new IOException("Could not delete " + mod, e.exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
id("inceptum.application-standalone-conventions")
|
||||
inceptum.`application-standalone`
|
||||
org.beryx.jlink
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("io.gitlab.jfronny.inceptum.Inceptum")
|
||||
mainModule.set("io.gitlab.jfronny.inceptum.launcher.dist")
|
||||
applicationName = "Inceptum"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":launcher"))
|
||||
implementation(project(":launcher-cli"))
|
||||
implementation(project(":launcher-imgui"))
|
||||
implementation(projects.launcher)
|
||||
implementation(projects.launcherCli)
|
||||
implementation(projects.launcherImgui)
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
|
@ -26,17 +33,103 @@ tasks.shadowJar {
|
|||
|
||||
publishing {
|
||||
publications {
|
||||
if (rootProject.hasProperty("dist.platformOnly")) {
|
||||
create<MavenPublication>("shadowed") {
|
||||
artifact(tasks.shadowJar) {
|
||||
builtBy(tasks.shadowJar)
|
||||
artifactId = "Inceptum"
|
||||
}
|
||||
create<MavenPublication>("mavenJava") {
|
||||
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) }
|
||||
|
||||
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"
|
||||
))
|
||||
}
|
||||
} else {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
from(components["java"])
|
||||
"windows" -> {
|
||||
installerType = "msi"
|
||||
installerOptions.addAll(listOf(
|
||||
"--win-per-user-install",
|
||||
"--win-dir-chooser",
|
||||
"--win-menu",
|
||||
"--win-upgrade-uuid", "180becd8-a867-40d4-86ef-20949cae68b5" // Update this UUID if you fork the project!!!
|
||||
))
|
||||
//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
|
||||
public void invoke(CommandArgs args) {
|
||||
LauncherEnv.updateBackend(new GuiEnvBackend());
|
||||
GuiMain.main();
|
||||
GuiMain.showGui();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -30,19 +30,25 @@ public class UpdateCheckCommand extends Command {
|
|||
Utils.LOGGER.error("Automatic updates are not supported without the wrapper");
|
||||
return;
|
||||
}
|
||||
UpdateMetadata updateUrl = BuildMetadata.IS_PUBLIC ? Updater.getUpdate() : null;
|
||||
if (updateUrl == null) {
|
||||
UpdateMetadata update;
|
||||
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");
|
||||
} else {
|
||||
if (install) {
|
||||
Utils.LOGGER.info("Installing from " + updateUrl);
|
||||
Utils.LOGGER.info("Installing from " + update);
|
||||
try {
|
||||
Updater.update(updateUrl, false);
|
||||
Updater.update(update, false);
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
Utils.LOGGER.error("Could not download update", e);
|
||||
}
|
||||
} else {
|
||||
Utils.LOGGER.info("An update was found: " + updateUrl);
|
||||
Utils.LOGGER.info("An update was found: " + update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
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 {
|
||||
val javagiVersion: String by rootProject.extra
|
||||
|
||||
implementation(libs.bundles.javagi)
|
||||
implementation(projects.launcher)
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.compilerArgs.add("--enable-preview")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
tasks.withType(KotlinCompile::class) { compilerOptions.freeCompilerArgs.addAll("-Xlambdas=indy") }
|
|
@ -0,0 +1,117 @@
|
|||
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 {
|
||||
var flags = DialogFlags.DESTROY_WITH_PARENT
|
||||
if (dialogParent != null) flags = flags.or(DialogFlags.MODAL)
|
||||
val dialog = StringInputDialog(
|
||||
dialogParent,
|
||||
flags,
|
||||
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,58 @@
|
|||
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.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 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>) {
|
||||
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()
|
||||
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,21 @@
|
|||
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 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,151 @@
|
|||
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.I18n
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.fixSubtitle
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.get
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.margin
|
||||
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()
|
||||
|
||||
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,58 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.control
|
||||
|
||||
import io.github.jwharm.javagi.gobject.SignalConnection
|
||||
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()
|
||||
}
|
||||
onBind {
|
||||
val li = it as ListItem
|
||||
val id = (li.item as StringObject).string
|
||||
val context = BindContextImpl(id, li)
|
||||
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,6 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.util
|
||||
|
||||
import io.gitlab.jfronny.commons.logging.Logger
|
||||
import io.gitlab.jfronny.inceptum.common.Utils
|
||||
|
||||
object Log : Logger by Utils.LOGGER
|
|
@ -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,178 @@
|
|||
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.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.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")
|
||||
Thread {
|
||||
try {
|
||||
for (step in Steps.STEPS) {
|
||||
if (state.isCancelled) {
|
||||
state.tryRemoveInstance()
|
||||
return@Thread
|
||||
}
|
||||
pState.incrementStep(step.name)
|
||||
step.execute(state)
|
||||
}
|
||||
state.clearSetupLock()
|
||||
} 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() }
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package io.gitlab.jfronny.inceptum.gtk.window.dialog
|
||||
|
||||
import io.gitlab.jfronny.inceptum.common.Utils
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||
import io.gitlab.jfronny.inceptum.gtk.util.Log
|
||||
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount
|
||||
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAuthAPI
|
||||
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAuthServer
|
||||
import org.gnome.gtk.*
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
|
||||
class MicrosoftLoginDialog(
|
||||
parent: Window?,
|
||||
account: MicrosoftAccount? = null,
|
||||
onClose: Runnable? = null
|
||||
) : MessageDialog(
|
||||
parent,
|
||||
flags(parent != null),
|
||||
MessageType.QUESTION,
|
||||
ButtonsType.CLOSE,
|
||||
I18n["auth.description"]
|
||||
) {
|
||||
constructor(parent: Window?, onClose: Runnable?) : this(parent, null, onClose)
|
||||
|
||||
init {
|
||||
title = I18n["auth.title"]
|
||||
val server = MicrosoftAuthServer(account)
|
||||
try {
|
||||
server.start()
|
||||
} catch (e: Exception) {
|
||||
Log.error("Could not start mc login server", e)
|
||||
}
|
||||
val finalize = Runnable {
|
||||
server.close()
|
||||
onClose?.run()
|
||||
}
|
||||
onResponse { responseId: Int ->
|
||||
when (ResponseType.of(responseId)) {
|
||||
ResponseType.CLOSE, ResponseType.CANCEL -> {
|
||||
finalize.run()
|
||||
close()
|
||||
}
|
||||
|
||||
ResponseType.DELETE_EVENT -> {
|
||||
finalize.run()
|
||||
destroy()
|
||||
}
|
||||
|
||||
else -> Log.error("Unexpected response type: $responseId")
|
||||
}
|
||||
}
|
||||
val btn = Button.withLabel(I18n["auth.open-browser"])
|
||||
(messageArea as Box).append(btn)
|
||||
btn.onClicked {
|
||||
try {
|
||||
Utils.openWebBrowser(URI(MicrosoftAuthAPI.MICROSOFT_LOGIN_URL))
|
||||
} catch (e: URISyntaxException) {
|
||||
Log.error("Could not open browser", e)
|
||||
}
|
||||
}
|
||||
onCloseRequest {
|
||||
finalize.run()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun flags(modal: Boolean): DialogFlags {
|
||||
var flags = DialogFlags.DESTROY_WITH_PARENT
|
||||
if (modal) flags = flags.or(DialogFlags.MODAL)
|
||||
return flags
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue