Compare commits
167 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 | |
Johannes Frohnmeyer | 3480e0d965 | |
Johannes Frohnmeyer | 58ffd7d11e | |
Johannes Frohnmeyer | 67bb89abe7 | |
Johannes Frohnmeyer | d2c0979d16 | |
Johannes Frohnmeyer | f7a3d5be53 | |
Johannes Frohnmeyer | da621e0eba | |
Johannes Frohnmeyer | cdf81fcb57 | |
Johannes Frohnmeyer | 5cc2b416be | |
Johannes Frohnmeyer | 165cad8e6b | |
Johannes Frohnmeyer | c7aafad507 | |
Johannes Frohnmeyer | f1d1979977 | |
Johannes Frohnmeyer | f4302a1a3f | |
Johannes Frohnmeyer | 8445bce486 | |
Johannes Frohnmeyer | 1f9f56d413 | |
Johannes Frohnmeyer | 81f9cd270a | |
Johannes Frohnmeyer | ad18164d69 | |
Johannes Frohnmeyer | 3dd66de0a7 | |
Johannes Frohnmeyer | be41e8d60d | |
Johannes Frohnmeyer | 2c2efcacda | |
Johannes Frohnmeyer | 63bf7df080 | |
Johannes Frohnmeyer | d777b9f39d | |
Johannes Frohnmeyer | 547889ce7f | |
Johannes Frohnmeyer | 35ffea6feb |
|
@ -5,4 +5,5 @@ run*/
|
|||
imgui.ini
|
||||
hs_err_pid*
|
||||
inceptum.log
|
||||
bin
|
||||
bin
|
||||
public
|
|
@ -1,94 +0,0 @@
|
|||
image: gradle:jdk18
|
||||
|
||||
variables:
|
||||
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
|
||||
|
||||
before_script:
|
||||
- export GRADLE_USER_HOME=`pwd`/.gradle
|
||||
|
||||
stages:
|
||||
- build
|
||||
- portable
|
||||
- deploy
|
||||
|
||||
build_test:
|
||||
stage: build
|
||||
script:
|
||||
- TIMESTAMP=$(date +%s)
|
||||
- gradle --build-cache build publish -Pflavor=nogui -Ppublic -Ptimestamp=$TIMESTAMP
|
||||
- gradle --build-cache build publish -Pflavor=fat -Ppublic -Ptimestamp=$TIMESTAMP
|
||||
- gradle --build-cache build publish -Pflavor=windows -Ppublic -Ptimestamp=$TIMESTAMP
|
||||
- gradle --build-cache build publish -Pflavor=linux -Ppublic -Ptimestamp=$TIMESTAMP
|
||||
- gradle --build-cache build publish -Pflavor=macos -Ppublic -Ptimestamp=$TIMESTAMP
|
||||
- mkdir -p build/libs
|
||||
- cp launcher/build/libs/* build/libs/
|
||||
- cp wrapper/build/libs/* build/libs/
|
||||
- cp launcher/build/libs/*-*-*-*.jar ./
|
||||
- cp wrapper/build/libs/*.exe wrapper.exe
|
||||
- cp wrapper/build/libs/*-all.jar wrapper.jar
|
||||
- for f in *-*-*.jar; do mv "$f" "latest-${f##*-}";done
|
||||
- mv latest-fat.jar latest.jar
|
||||
- unzip -p latest.jar version.json > version.json
|
||||
artifacts:
|
||||
paths:
|
||||
- build/libs
|
||||
- latest.jar
|
||||
- wrapper.jar
|
||||
- wrapper.exe
|
||||
- latest-*.jar
|
||||
- version.json
|
||||
expire_in: 2 days
|
||||
only:
|
||||
- master
|
||||
|
||||
portable:
|
||||
stage: portable
|
||||
image: archlinux:latest
|
||||
script:
|
||||
- pacman -Sy p7zip curl jq --noconfirm
|
||||
- mkdir -p portable/run/libraries/io/gitlab/jfronny/inceptum/Inceptum
|
||||
- mkdir -p portable/jvm
|
||||
- cp wrapper.jar portable/
|
||||
- main="$(find build/libs/ -type f -iname "Inceptum-*-windows.jar")"
|
||||
- dir="${main##*/Inceptum-}"
|
||||
- dir="portable/run/libraries/io/gitlab/jfronny/inceptum/Inceptum/${dir%%-windows*}"
|
||||
- mkdir -p "$dir"
|
||||
- mv "$main" "$dir/${main##*/}"
|
||||
- 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/17/ga/windows/x64/jdk/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
|
||||
|
||||
deploy:
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG && '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^master/'
|
||||
stage: deploy
|
||||
script:
|
||||
- gradle --build-cache build publish -Pflavor=nogui -Ppublic -Prelease
|
||||
- gradle --build-cache build publish -Pflavor=fat -Ppublic -Prelease
|
||||
- gradle --build-cache build publish -Pflavor=windows -Ppublic -Prelease
|
||||
- gradle --build-cache build publish -Pflavor=linux -Ppublic -Prelease
|
||||
- gradle --build-cache build publish -Pflavor=macos -Ppublic -Prelease
|
||||
|
||||
pages:
|
||||
image: python:3.8-buster
|
||||
stage: deploy
|
||||
script:
|
||||
- pip install mkdocs
|
||||
- mkdocs build
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
only:
|
||||
- master
|
|
@ -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
|
||||
|
|
13
README.md
13
README.md
|
@ -1,19 +1,22 @@
|
|||
# Inceptum Launcher
|
||||
|
||||
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
|
||||
|
||||
Inceptum utilizes code/libraries/assets from:
|
||||
|
||||
- [ATLauncher](https://github.com/ATLauncher/ATLauncher): Microsoft authentication
|
||||
- [JLHTTP](https://www.freeutils.net/source/jlhttp/): Used in MS Auth
|
||||
- [MultiMC](https://github.com/MultiMC/Launcher): Used as a reference for some implementations
|
||||
- [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
|
||||
- [slf4j](https://github.com/qos-ch/slf4j): Used for logging
|
||||
- [logback](https://github.com/qos-ch/logback): An implementation of slf4j
|
||||
- [JGit](https://www.eclipse.org/jgit/): Used to allow syncing repositories
|
||||
- [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)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
[book]
|
||||
authors = ["JFronny"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "docs"
|
||||
title = "LibJF"
|
||||
description = "Inceptum Docs"
|
||||
|
||||
[build]
|
||||
build-dir = "public"
|
||||
|
||||
[output.html]
|
||||
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,39 +1,73 @@
|
|||
import org.gradle.internal.os.OperatingSystem
|
||||
import org.ajoberstar.grgit.Grgit
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
println("Building Inceptum $currentVer")
|
||||
|
||||
allprojects {
|
||||
version = currentVer + if (project.hasProperty("release")) "" else "-" + (if (project.hasProperty("timestamp")) project.property("timestamp") else "${System.currentTimeMillis() / 1000L}")
|
||||
version = rootProject.version
|
||||
group = "io.gitlab.jfronny.inceptum"
|
||||
}
|
||||
|
||||
val lwjglVersion by extra("3.3.1")
|
||||
val imguiVersion by extra("1.86.4")
|
||||
val logbackVersion by extra("1.3.0-alpha15")
|
||||
val jfCommonsVersion by extra("2022.7.4+11-13-3")
|
||||
val jgitVersion by extra("6.2.0.202206071550-r")
|
||||
val flavorProp: String by extra(if (project.hasProperty("flavor")) "${project.property("flavor")}" else "custom")
|
||||
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()
|
||||
})
|
||||
// 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 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 isRelease by extra(project.hasProperty("release"))
|
||||
|
||||
val buildTime by extra(System.currentTimeMillis())
|
||||
val wrapperVersion by extra(1)
|
||||
|
||||
tasks.register("exportMetadata") {
|
||||
doLast {
|
||||
projectDir.resolve("version.json").writeText(
|
||||
"""
|
||||
{
|
||||
"wrapperVersion": $wrapperVersion,
|
||||
"version": "$version",
|
||||
"buildTime": $buildTime,
|
||||
"isPublic": $isPublic,
|
||||
"isRelease": $isRelease,
|
||||
"jvm": ${project(":common").extra["javaVersion"]},
|
||||
"repositories": [
|
||||
"https://repo.maven.apache.org/maven2/",
|
||||
"https://maven.frohnmeyer-wds.de/artifacts/"
|
||||
],
|
||||
"natives": {
|
||||
"windows": [
|
||||
"org.lwjgl:lwjgl:$lwjglVersion:natives-windows",
|
||||
"org.lwjgl:lwjgl-opengl:$lwjglVersion:natives-windows",
|
||||
"org.lwjgl:lwjgl-glfw:$lwjglVersion:natives-windows",
|
||||
"org.lwjgl:lwjgl-tinyfd:$lwjglVersion:natives-windows",
|
||||
"io.github.spair:imgui-java-natives-windows:$imguiVersion"
|
||||
],
|
||||
"linux": [
|
||||
"org.lwjgl:lwjgl:$lwjglVersion:natives-linux",
|
||||
"org.lwjgl:lwjgl-opengl:$lwjglVersion:natives-linux",
|
||||
"org.lwjgl:lwjgl-glfw:$lwjglVersion:natives-linux",
|
||||
"org.lwjgl:lwjgl-tinyfd:$lwjglVersion:natives-linux",
|
||||
"io.github.spair:imgui-java-natives-linux:$imguiVersion"
|
||||
],
|
||||
"macos": [
|
||||
"org.lwjgl:lwjgl:$lwjglVersion:natives-macos",
|
||||
"org.lwjgl:lwjgl-opengl:$lwjglVersion:natives-macos",
|
||||
"org.lwjgl:lwjgl-glfw:$lwjglVersion:natives-macos",
|
||||
"org.lwjgl:lwjgl-tinyfd:$lwjglVersion:natives-macos",
|
||||
"io.github.spair:imgui-java-natives-macos:$imguiVersion"
|
||||
]
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven("https://maven.frohnmeyer-wds.de/artifacts")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
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" "$@"
|
|
@ -0,0 +1,8 @@
|
|||
rootProject.name="inceptum-conventions"
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import de.undercouch.gradle.tasks.download.Download
|
||||
import java.io.FileOutputStream
|
||||
|
||||
plugins {
|
||||
application
|
||||
id("inceptum.java")
|
||||
com.github.johnrengelman.shadow
|
||||
de.undercouch.download
|
||||
}
|
||||
|
||||
abstract class FileOutput : DefaultTask() {
|
||||
@get:OutputFile
|
||||
abstract var output: File
|
||||
}
|
||||
|
||||
val bootstrapVersion = "0.3.2"
|
||||
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.layout.buildDirectory)
|
||||
}
|
||||
|
||||
val nativeExe by tasks.registering(FileOutput::class) {
|
||||
dependsOn(downloadBootstrap)
|
||||
dependsOn(tasks.shadowJar)
|
||||
|
||||
output = project.layout.buildDirectory.file("libs/${project.name}-${project.version}.exe").get().asFile
|
||||
outputs.upToDateWhen { false }
|
||||
|
||||
doFirst {
|
||||
output.delete()
|
||||
}
|
||||
|
||||
doLast {
|
||||
output.createNewFile()
|
||||
FileOutputStream(output).use { w ->
|
||||
w.write(downloadBootstrap.get().outputFiles.first().readBytes())
|
||||
w.write(tasks.shadowJar.get().archiveFile.get().asFile.readBytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rootProject.extra["flavor"] == "windows") {
|
||||
tasks.build.get().dependsOn(nativeExe)
|
||||
}
|
||||
|
||||
tasks.runShadow {
|
||||
workingDir = rootProject.projectDir
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
plugins {
|
||||
application
|
||||
id("inceptum.java")
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
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")
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
plugins {
|
||||
id("inceptum.java")
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +1,32 @@
|
|||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
import io.gitlab.jfronny.scripts.*
|
||||
import javax.lang.model.element.Modifier
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
setUrl("https://gitlab.com/api/v4/projects/35745143/packages/maven")
|
||||
}
|
||||
plugins {
|
||||
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"]}")
|
||||
implementation("io.gitlab.jfronny:commons-slf4j:${rootProject.extra["jfCommonsVersion"]}")
|
||||
implementation("ch.qos.logback:logback-classic:${rootProject.extra["logbackVersion"]}")
|
||||
api(libs.bundles.commons)
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
filesMatching("version.json") {
|
||||
expand(
|
||||
"version" to project.version,
|
||||
"flavor" to rootProject.extra["flavorProp"],
|
||||
"isPublic" to rootProject.extra["isPublic"],
|
||||
"isRelease" to rootProject.extra["isRelease"],
|
||||
"jvm" to project.java.targetCompatibility
|
||||
)
|
||||
val javaVersion by extra(project.java.targetCompatibility)
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
generate(project) {
|
||||
`class`("io.gitlab.jfronny.inceptum.common", "BuildMetadata") {
|
||||
modifiers(Modifier.PUBLIC)
|
||||
val modifiers = arrayOf(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
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.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 {
|
||||
if (!Files.exists(MetaHolder.CONFIG_PATH.getParent()))
|
||||
Files.createDirectories(MetaHolder.CONFIG_PATH.getParent());
|
||||
if (!Files.exists(MetaHolder.CONFIG_PATH)) {
|
||||
Path gLaunch2 = MetaHolder.BASE_PATH.resolve("glaunch2.json");
|
||||
Path json = MetaHolder.BASE_PATH.resolve("inceptum.json");
|
||||
if (Files.exists(gLaunch2)) {
|
||||
Files.move(gLaunch2, MetaHolder.CONFIG_PATH);
|
||||
} else if (Files.exists(json)) {
|
||||
Files.move(json, MetaHolder.CONFIG_PATH);
|
||||
} else {
|
||||
saveConfig();
|
||||
}
|
||||
}
|
||||
GC_InceptumConfig.read(MetaHolder.CONFIG_PATH);
|
||||
}
|
||||
|
||||
public static void saveConfig() {
|
||||
try {
|
||||
GC_InceptumConfig.write(MetaHolder.CONFIG_PATH);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not save config", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
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);
|
||||
HttpClient.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
|
||||
InceptumConfig.load();
|
||||
}
|
||||
|
||||
public static Logger defaultFactory(String name) {
|
||||
return StdoutLogger.fancy(name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
import io.gitlab.jfronny.commons.OSUtils;
|
||||
|
||||
import java.nio.file.*;
|
||||
|
||||
public class MetaHolder {
|
||||
public static final Path BASE_PATH;
|
||||
|
||||
static {
|
||||
if (System.getProperty("inceptum.base") == null) {
|
||||
Path runDir = getPath("run");
|
||||
if (!BuildMetadata.IS_RELEASE) BASE_PATH = runDir;
|
||||
else if (Files.exists(runDir)) BASE_PATH = runDir;
|
||||
else {
|
||||
Path configsDir = switch (OSUtils.TYPE) {
|
||||
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("XDG_CONFIG_HOME");
|
||||
if (s == null)
|
||||
yield getPath(System.getProperty("user.home")).resolve(".config");
|
||||
else
|
||||
yield getPath(s);
|
||||
}
|
||||
};
|
||||
BASE_PATH = configsDir.resolve("Inceptum");
|
||||
}
|
||||
} else {
|
||||
BASE_PATH = getPath(System.getProperty("inceptum.base"));
|
||||
}
|
||||
}
|
||||
|
||||
public static final Path ACCOUNTS_PATH = BASE_PATH.resolve("accounts.json");
|
||||
public static final Path CONFIG_PATH = BASE_PATH.resolve("inceptum.json5");
|
||||
public static final Path WRAPPER_CONFIG_PATH = BASE_PATH.resolve("wrapper.json");
|
||||
public static final Path NATIVES_DIR = BASE_PATH.resolve("natives");
|
||||
public static final Path FORCE_LOAD_PATH = NATIVES_DIR.resolve("forceload");
|
||||
public static final Path LIBRARIES_DIR = BASE_PATH.resolve("libraries");
|
||||
public static final Path ASSETS_DIR = BASE_PATH.resolve("assets");
|
||||
public static final Path INSTANCE_DIR = BASE_PATH.resolve("instances");
|
||||
public static final Path CACHE_DIR = BASE_PATH.resolve("cache");
|
||||
private static boolean isWrapper = false;
|
||||
|
||||
private static Path getPath(String text) {
|
||||
return Paths.get(text).toAbsolutePath().normalize();
|
||||
}
|
||||
|
||||
public static void setWrapperFlag() {
|
||||
isWrapper = true;
|
||||
}
|
||||
|
||||
public static boolean isWrapper() {
|
||||
return isWrapper;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
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.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
public class Net {
|
||||
private static final ObjectCache OBJECT_CACHE = new ObjectCache(MetaHolder.CACHE_DIR);
|
||||
|
||||
public static byte[] downloadData(String url) throws IOException, URISyntaxException {
|
||||
try (InputStream is = HttpClient.get(url).sendInputStream()) {
|
||||
return is.readAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] downloadData(String url, String sha1) throws IOException, URISyntaxException {
|
||||
byte[] buf = downloadData(url);
|
||||
if (sha1 == null) return buf;
|
||||
if (!HashUtils.sha1(buf).equals(sha1)) throw new IOException("Invalid hash");
|
||||
return buf;
|
||||
}
|
||||
|
||||
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, 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, ThrowingFunction<String, T, IOException> func, String apiKey) throws IOException {
|
||||
return downloadObject(url, () -> downloadStringAuthenticated(url, apiKey), func, 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, String sha1, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
||||
return downloadObject(url, () -> downloadString(url, sha1), func, cache);
|
||||
}
|
||||
|
||||
private static <T> T downloadObject(String url, ThrowingSupplier<String, Exception> sourceString, ThrowingFunction<String, T, IOException> func, boolean cache) throws IOException {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public static String buildUrl(String host, String url, Map<String, String> params) {
|
||||
StringBuilder res = new StringBuilder(host);
|
||||
if (res.toString().endsWith("/")) res = new StringBuilder(res.substring(0, res.length() - 1));
|
||||
if (url.startsWith("/")) res.append(url);
|
||||
else res.append("/").append(url);
|
||||
int i = 0;
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
res.append(i++ == 0 ? '?' : '&')
|
||||
.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
|
||||
.append('=')
|
||||
.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
|
||||
}
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
public static String downloadString(String url) throws IOException, URISyntaxException {
|
||||
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));
|
||||
}
|
||||
|
||||
public static void downloadFile(String url, String sha1, Path path) throws IOException, URISyntaxException {
|
||||
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
|
||||
Files.write(path, downloadData(url, sha1));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
import io.gitlab.jfronny.commons.OSUtils;
|
||||
import io.gitlab.jfronny.inceptum.common.api.MavenApi;
|
||||
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.*;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
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(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();
|
||||
});
|
||||
}
|
||||
|
||||
public static void update(UpdateMetadata source, boolean relaunch) throws IOException, URISyntaxException {
|
||||
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)));
|
||||
|
||||
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())) {
|
||||
Set<String> natives = new LinkedHashSet<>();
|
||||
for (String lib : source.natives().get(Utils.getCurrentFlavor())) {
|
||||
downloadLibrary(source.repositories(), lib, natives);
|
||||
}
|
||||
currentLibraries.addAll(natives);
|
||||
config.natives().put(Utils.getCurrentFlavor(), natives);
|
||||
}
|
||||
|
||||
GC_WrapperConfig.write(config, MetaHolder.WRAPPER_CONFIG_PATH);
|
||||
|
||||
if (relaunch) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
try {
|
||||
new ProcessBuilder(OSUtils.getJvmBinary(),
|
||||
"-cp",
|
||||
buildClasspath(currentLibraries.stream())
|
||||
.map(Path::toString)
|
||||
.collect(Collectors.joining(String.valueOf(File.pathSeparatorChar)))
|
||||
).inheritIO().start();
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not relaunch", e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Path> getLaunchClasspath(WrapperConfig wrapperConfig) throws IOException, URISyntaxException {
|
||||
Set<String> natives = wrapperConfig.natives().get(Utils.getCurrentFlavor());
|
||||
if (natives == null) natives = new LinkedHashSet<>();
|
||||
Set<String> libs = wrapperConfig.libraries();
|
||||
if (libs == null) libs = new LinkedHashSet<>();
|
||||
|
||||
boolean configChanged = false;
|
||||
|
||||
for (String lib : libs) {
|
||||
Path p = ArtifactMeta.parse(lib).getLocalPath();
|
||||
if (!Files.exists(p)) {
|
||||
configChanged = true;
|
||||
downloadLibrary(wrapperConfig.repositories(), lib, libs);
|
||||
}
|
||||
}
|
||||
for (String lib : natives) {
|
||||
Path p = ArtifactMeta.parse(lib).getLocalPath();
|
||||
if (!Files.exists(p)) {
|
||||
configChanged = true;
|
||||
downloadLibrary(wrapperConfig.repositories(), lib, natives);
|
||||
}
|
||||
}
|
||||
|
||||
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(ArtifactMeta::parse).map(ArtifactMeta::getLocalPath);
|
||||
}
|
||||
|
||||
private static DependencyNode downloadLibrary(Set<String> repositories, final String artifact, Set<String> libraries) throws IOException, URISyntaxException {
|
||||
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, 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();
|
||||
dependencies.add(downloadLibrary(repositories, mvnName, libraries));
|
||||
}
|
||||
}
|
||||
MavenApi.downloadLibrary(repository, meta);
|
||||
libraries.add(artifact);
|
||||
return new DependencyNode(artifact, dependencies);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public static @Nullable UpdateMetadata check(UpdateChannel channel, boolean versionCompare, boolean checkEnv, Consumer<UpdateChannel> channelInvalid) throws UpdateCheckException {
|
||||
try {
|
||||
UpdateMetadata experimental = Net.downloadObject(ARTIFACTS_URL + "version.json", json -> GC_UpdateMetadata.read(json));
|
||||
UpdateMetadata stable = null;
|
||||
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);
|
||||
}
|
||||
UpdateMetadata info = switch (channel) {
|
||||
case CI -> experimental;
|
||||
case Stable -> stable;
|
||||
};
|
||||
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 e) {
|
||||
Utils.LOGGER.error("Could not check for updates", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package io.gitlab.jfronny.inceptum.common;
|
||||
|
||||
import io.gitlab.jfronny.commons.OSUtils;
|
||||
import io.gitlab.jfronny.commons.io.JFiles;
|
||||
import io.gitlab.jfronny.commons.logging.Logger;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
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();
|
||||
|
||||
public static void openWebBrowser(URI uri) {
|
||||
try {
|
||||
if (OSUtils.TYPE == OSUtils.Type.LINUX && OSUtils.executablePathContains("xdg-open")) {
|
||||
Runtime.getRuntime().exec(new String[]{"xdg-open", uri.toString()});
|
||||
} else if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||
Desktop.getDesktop().browse(uri);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Utils.LOGGER.error("Error opening web browser!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void openFile(File file) {
|
||||
try {
|
||||
if (OSUtils.TYPE == OSUtils.Type.LINUX && OSUtils.executablePathContains("xdg-open")) {
|
||||
Runtime.getRuntime().exec(new String[]{"xdg-open", file.getAbsoluteFile().toString()});
|
||||
} else if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||
Desktop.getDesktop().open(file);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Utils.LOGGER.error("Error opening file!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static FileSystem openZipFile(Path zip, boolean create) throws IOException, URISyntaxException {
|
||||
return JFiles.openZipFile(zip, create, SYSTEM_LOADER);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // Called through reflection from wrapper
|
||||
public static void wrapperInit(ClassLoader loader) {
|
||||
SYSTEM_LOADER = loader;
|
||||
MetaHolder.setWrapperFlag();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return The joined string
|
||||
*/
|
||||
public static String join(String separator, String... segments) {
|
||||
return Arrays.stream(segments)
|
||||
.map(s -> s.startsWith(separator) ? s.substring(separator.length()) : s)
|
||||
.map(s -> s.endsWith(separator) ? s.substring(0, s.length() - separator.length()) : s)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.collect(Collectors.joining(separator));
|
||||
}
|
||||
|
||||
public static String getCurrentFlavor() {
|
||||
return switch (OSUtils.TYPE) {
|
||||
case WINDOWS -> "windows";
|
||||
case MAC_OS -> "macos";
|
||||
case LINUX -> "linux";
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
package io.gitlab.jfronny.inceptum.common.api;
|
||||
|
||||
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;
|
||||
|
||||
import javax.xml.parsers.*;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class MavenApi {
|
||||
private static final DocumentBuilder FACTORY;
|
||||
private static final Set<String> RUNTIME_SCOPES = Set.of("compile", "runtime");
|
||||
|
||||
static {
|
||||
try {
|
||||
FACTORY = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
} catch (ParserConfigurationException e) {
|
||||
throw new RuntimeException("Could not create document builder", e);
|
||||
}
|
||||
}
|
||||
|
||||
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, 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();
|
||||
if (!"project".equals(doc.getDocumentElement().getNodeName())) throw new IOException("Illegal document name");
|
||||
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" -> 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 (groupId == null) {
|
||||
for (Node child : children(node)) {
|
||||
switch (child.getNodeName()) {
|
||||
case "groupId" -> {
|
||||
if (groupId == null) {
|
||||
groupId = child.getTextContent();
|
||||
}
|
||||
}
|
||||
case "version" -> {
|
||||
if (version == null) {
|
||||
version = child.getTextContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "groupId" -> groupId = node.getTextContent();
|
||||
case "artifactId" -> {
|
||||
artifactId = node.getTextContent();
|
||||
}
|
||||
case "version" -> {
|
||||
version = node.getTextContent();
|
||||
}
|
||||
case "packaging" -> packaging = node.getTextContent();
|
||||
case "dependencies" -> {
|
||||
dependencies = new LinkedList<>();
|
||||
for (Node dep : children(node)) {
|
||||
MavenDependency resolved = parseDependency(dep);
|
||||
if (resolved != null) {
|
||||
dependencies.add(resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
case "classifier" -> classifier = node.getTextContent();
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
String groupId = null;
|
||||
String artifactId = null;
|
||||
String version = null;
|
||||
String scope = null;
|
||||
for (Node node : children(doc)) {
|
||||
switch (node.getNodeName()) {
|
||||
case "groupId" -> groupId = node.getTextContent();
|
||||
case "artifactId" -> artifactId = node.getTextContent();
|
||||
case "version" -> version = node.getTextContent();
|
||||
case "scope" -> {
|
||||
scope = node.getTextContent();
|
||||
if (!RUNTIME_SCOPES.contains(scope)) return null;
|
||||
}
|
||||
case "optional" -> {
|
||||
if (node.getTextContent().equals("true")) return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 " + groupId + ":" + artifactId + " lacks version");
|
||||
}
|
||||
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) {
|
||||
if (node.getNodeType() == Node.TEXT_NODE && node.getTextContent().isBlank()) return true;
|
||||
if (node.getNodeType() == Node.COMMENT_NODE) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
||||
|
||||
public enum UpdateChannel {
|
||||
Stable, CI
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
||||
|
||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
||||
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@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) {
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.gitlab.jfronny.inceptum.common.model.inceptum;
|
||||
|
||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
||||
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package io.gitlab.jfronny.inceptum.common.model.maven;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
public record DependencyNode(String name, Set<DependencyNode> dependencies) {
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
generateTree(sb, "", "");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void generateTree(StringBuilder sb, String prefix, String childrenPrefix) {
|
||||
sb.append(prefix).append(name).append('\n');
|
||||
for (Iterator<DependencyNode> it = dependencies.iterator(); it.hasNext(); ) {
|
||||
DependencyNode next = it.next();
|
||||
if (it.hasNext()) {
|
||||
next.generateTree(sb, childrenPrefix + "├── ", childrenPrefix + "│ ");
|
||||
} else {
|
||||
next.generateTree(sb, childrenPrefix + "└── ", childrenPrefix + " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package io.gitlab.jfronny.inceptum.common.model.maven;
|
||||
|
||||
public record MavenDependency(String groupId, String artifactId, String version, String scope) {
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package io.gitlab.jfronny.inceptum.common.model.maven;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record Pom(String modelVersion,
|
||||
String groupId,
|
||||
String artifactId,
|
||||
String version,
|
||||
String classifier,
|
||||
String snapshotVersion,
|
||||
@Nullable String packaging,
|
||||
@Nullable List<MavenDependency> dependencies) {
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.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.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.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.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.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.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.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.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.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,25 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.model.inceptum;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class Config {
|
||||
public boolean snapshots = false;
|
||||
public boolean darkTheme = false;
|
||||
public boolean enforceAccount = true;
|
||||
public String lastAccount;
|
||||
public String offlineAccountLastName = null;
|
||||
public UpdateChannel channel = UpdateChannel.Stable;
|
||||
public GitConfig git = new GitConfig();
|
||||
|
||||
public static class GitConfig {
|
||||
public Map<String, GitAuth> instanceAuths;
|
||||
public String commitUsername = "Inceptum";
|
||||
public String commitMail = "inceptum@jfronny.gitlab.io";
|
||||
public Boolean signCommits = false;
|
||||
|
||||
public static class GitAuth {
|
||||
public String username;
|
||||
public String password;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.model.inceptum;
|
||||
|
||||
import io.gitlab.jfronny.commons.ComparableVersion;
|
||||
|
||||
public class InceptumVersion {
|
||||
public ComparableVersion version;
|
||||
public String flavor;
|
||||
public Boolean isPublic;
|
||||
public Boolean isRelease;
|
||||
public Integer jvm;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.model.inceptum;
|
||||
|
||||
public enum UpdateChannel {
|
||||
Stable, CI
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.model.inceptum;
|
||||
|
||||
import io.gitlab.jfronny.commons.ComparableVersion;
|
||||
|
||||
public record UpdateInfo(String url, String sha1, ComparableVersion newVersion) {
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.util;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.Config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ConfigHolder {
|
||||
public static Config CONFIG;
|
||||
|
||||
public static void load() throws IOException {
|
||||
if (!Files.exists(MetaHolder.CONFIG_PATH.getParent())) Files.createDirectories(MetaHolder.CONFIG_PATH.getParent());
|
||||
if (!Files.exists(MetaHolder.CONFIG_PATH)) {
|
||||
Path gLaunch2 = MetaHolder.BASE_PATH.resolve("glaunch2.json");
|
||||
if (Files.exists(gLaunch2)) {
|
||||
Files.move(gLaunch2, MetaHolder.CONFIG_PATH);
|
||||
}
|
||||
else {
|
||||
Utils.writeObject(MetaHolder.CONFIG_PATH, new Config());
|
||||
}
|
||||
}
|
||||
CONFIG = Utils.loadObject(MetaHolder.CONFIG_PATH, Config.class);
|
||||
boolean changed = false;
|
||||
if (CONFIG.git == null) {
|
||||
CONFIG.git = new Config.GitConfig();
|
||||
changed = true;
|
||||
}
|
||||
if (CONFIG.git.instanceAuths == null) {
|
||||
CONFIG.git.instanceAuths = new HashMap<>();
|
||||
changed = true;
|
||||
}
|
||||
if (changed) saveConfig();
|
||||
}
|
||||
|
||||
public static void saveConfig() {
|
||||
try {
|
||||
Utils.writeObject(MetaHolder.CONFIG_PATH, CONFIG);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not save config", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.util;
|
||||
|
||||
import io.gitlab.jfronny.commons.HttpUtils;
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class InceptumEnvironmentInitializer {
|
||||
public static void initialize() throws IOException {
|
||||
HttpUtils.setUserAgent("jfmods/inceptum/" + MetaHolder.VERSION.version.toString());
|
||||
GsonHolder.register();
|
||||
ConfigHolder.load();
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.util;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.AppenderBase;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
public class MapAppender extends AppenderBase<ILoggingEvent> {
|
||||
public static final ConcurrentMap<String, ILoggingEvent> EVENT_MAP = new ConcurrentHashMap<>();
|
||||
public static final Set<String> LOG = new LinkedHashSet<>();
|
||||
|
||||
@Override
|
||||
protected void append(ILoggingEvent event) {
|
||||
EVENT_MAP.put(Instant.now().toString(), event);
|
||||
LOG.add(event.getLevel().toString() +
|
||||
" | " +
|
||||
Instant.now().toString() +
|
||||
" | [" +
|
||||
event.getThreadName() +
|
||||
"] " +
|
||||
event.getFormattedMessage());
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.util;
|
||||
|
||||
import io.gitlab.jfronny.commons.*;
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.*;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.file.*;
|
||||
|
||||
public class MetaHolder {
|
||||
public static final InceptumVersion VERSION;
|
||||
public static final Path BASE_PATH;
|
||||
|
||||
static {
|
||||
try (InputStream is = MetaHolder.class.getClassLoader().getResourceAsStream("version.json");
|
||||
InputStreamReader isr = new InputStreamReader(is)) {
|
||||
VERSION = GsonHolder.getGson().fromJson(isr, InceptumVersion.class);
|
||||
if (System.getProperty("inceptum.base") == null) {
|
||||
Path runDir = getRunDir();
|
||||
BASE_PATH = VERSION.isRelease && !Files.exists(runDir)
|
||||
? getConfigPath().resolve("Inceptum")
|
||||
: runDir;
|
||||
}
|
||||
else {
|
||||
BASE_PATH = Paths.get(System.getProperty("inceptum.base"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not get version info", e);
|
||||
}
|
||||
}
|
||||
public static final Path ACCOUNTS_PATH = BASE_PATH.resolve("accounts.json");
|
||||
public static final Path CONFIG_PATH = BASE_PATH.resolve("inceptum.json");
|
||||
public static final Path NATIVES_DIR = BASE_PATH.resolve("natives");
|
||||
public static final Path FORCE_LOAD_PATH = NATIVES_DIR.resolve("forceload");
|
||||
public static final Path LIBRARIES_DIR = BASE_PATH.resolve("libraries");
|
||||
public static final Path ASSETS_DIR = BASE_PATH.resolve("assets");
|
||||
public static final Path INSTANCE_DIR = BASE_PATH.resolve("instances");
|
||||
public static final Path CACHE_DIR = BASE_PATH.resolve("cache");
|
||||
private static boolean isWrapper = false;
|
||||
|
||||
private static Path getConfigPath() {
|
||||
return switch (OSUtils.TYPE) {
|
||||
case WINDOWS -> Paths.get(System.getenv("APPDATA"));
|
||||
case MAC_OS -> Paths.get(System.getProperty("user.home")).resolve("Library").resolve("Application Support");
|
||||
case LINUX -> {
|
||||
String s = System.getenv().get("XDG_CONFIG_HOME");
|
||||
if (s == null)
|
||||
yield Paths.get(System.getProperty("user.home")).resolve(".config");
|
||||
else
|
||||
yield Paths.get(s);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Path getRunDir() {
|
||||
Path simpleRunDir = Path.of(".").resolve("run");
|
||||
if (Files.exists(simpleRunDir)) return simpleRunDir;
|
||||
URL url = MetaHolder.class.getProtectionDomain().getCodeSource().getLocation();
|
||||
String p = url.getPath();
|
||||
if (p.endsWith(".jar") && !p.endsWith("/build/libs/wrapper-" + VERSION.version + ".jar")) {
|
||||
try {
|
||||
return new File(url.toURI()).toPath().getParent().resolve("run");
|
||||
} catch (URISyntaxException e) {
|
||||
Utils.LOGGER.error("Could not resolve dev env run dir");
|
||||
return simpleRunDir;
|
||||
}
|
||||
} else {
|
||||
System.out.println("Not running in a jar, using ./run");
|
||||
return simpleRunDir;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setWrapperFlag() {
|
||||
isWrapper = true;
|
||||
}
|
||||
|
||||
public static boolean isWrapper() {
|
||||
return isWrapper;
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.util;
|
||||
|
||||
import io.gitlab.jfronny.commons.ComparableVersion;
|
||||
import io.gitlab.jfronny.commons.HttpUtils;
|
||||
import io.gitlab.jfronny.inceptum.model.gitlab.*;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.InceptumVersion;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.UpdateChannel;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.UpdateInfo;
|
||||
import io.gitlab.jfronny.inceptum.util.api.GitlabApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class UpdateChecker {
|
||||
private static final long PROJECT_ID = 30862253L;
|
||||
|
||||
public static UpdateInfo check(UpdateChannel channel, ComparableVersion current, String flavor, Consumer<UpdateChannel> channelInvalid) {
|
||||
try {
|
||||
int jvm = Runtime.version().feature();
|
||||
if (flavor.equals("custom")) {
|
||||
Utils.LOGGER.error("Custom build, skipping update check");
|
||||
return null;
|
||||
}
|
||||
GitlabProject project = GitlabApi.getProject(PROJECT_ID);
|
||||
GitlabPackage experimental = null;
|
||||
ComparableVersion experimentalVersion = null;
|
||||
GitlabPackage stable = null;
|
||||
ComparableVersion stableVersion = null;
|
||||
packageLoop: for (GitlabPackage info : GitlabApi.getPackages(project)) {
|
||||
if (info.status.equals("default") && info.name.equals("io/gitlab/jfronny/inceptum/Inceptum")) {
|
||||
for (GitlabPipeline pipeline : info.pipelines) {
|
||||
for (GitlabJob job : GitlabApi.getJobs(project, pipeline.id)) {
|
||||
if (!job.name.equals("build_test")) continue;
|
||||
try {
|
||||
InceptumVersion iv = HttpUtils.get(GitlabApi.PROJECTS + project.id + "/jobs/" + job.id + "/artifacts/version.json").sendSerialized(InceptumVersion.class);
|
||||
if (iv.jvm > jvm) {
|
||||
Utils.LOGGER.error("A newer JVM is required to use the latest inceptum version. Please update!");
|
||||
continue packageLoop;
|
||||
}
|
||||
}
|
||||
catch (IOException | URISyntaxException e) {
|
||||
continue packageLoop;
|
||||
}
|
||||
}
|
||||
if (pipeline.ref.equals("master") && pipeline.status.equals("success")) {
|
||||
ComparableVersion cvNew = new ComparableVersion(info.version);
|
||||
if (experimentalVersion == null || experimentalVersion.compareTo(cvNew) < 0) {
|
||||
experimental = info;
|
||||
experimentalVersion = cvNew;
|
||||
}
|
||||
if (!info.version.contains("-") && (stableVersion == null || stableVersion.compareTo(cvNew) < 0)) {
|
||||
stable = info;
|
||||
stableVersion = cvNew;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (experimental == null) {
|
||||
throw new IOException("No version could be found");
|
||||
}
|
||||
else if (stable == null && channel == UpdateChannel.Stable) {
|
||||
channel = UpdateChannel.CI;
|
||||
channelInvalid.accept(channel);
|
||||
}
|
||||
GitlabPackage info = switch (channel) {
|
||||
case CI -> experimental;
|
||||
case Stable -> stable;
|
||||
};
|
||||
Utils.LOGGER.info("Latest version is " + info.version + ", current is " + current);
|
||||
if (current.compareTo(new ComparableVersion(info.version)) >= 0) {
|
||||
Utils.LOGGER.info("Up-to-date");
|
||||
return null;
|
||||
}
|
||||
GitlabPackageFile file = GitlabApi.getFile(project, info, f -> f.file_name.endsWith('-' + flavor + ".jar"));
|
||||
if (file == null)
|
||||
Utils.LOGGER.error("No valid package was discovered");
|
||||
else return new UpdateInfo("https://gitlab.com/" + project.path_with_namespace + "/-/package_files/" + file.id + "/download", file.file_sha1, new ComparableVersion(info.version));
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
Utils.LOGGER.error("Could not check for updates", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,275 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.util;
|
||||
|
||||
import io.gitlab.jfronny.commons.*;
|
||||
import io.gitlab.jfronny.commons.io.MultiAccessFileSystem;
|
||||
import io.gitlab.jfronny.commons.log.Logger;
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
|
||||
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
|
||||
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
||||
import io.gitlab.jfronny.inceptum.util.cache.GsonFileCache;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class Utils {
|
||||
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();
|
||||
private static final GsonFileCache OBJECT_CACHE = new GsonFileCache(MetaHolder.CACHE_DIR);
|
||||
|
||||
public static byte[] downloadData(String url) throws IOException, URISyntaxException {
|
||||
try (InputStream is = HttpUtils.get(url).sendInputStream()) {
|
||||
return is.readAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] downloadData(String url, String sha1) throws IOException, URISyntaxException {
|
||||
byte[] buf = downloadData(url);
|
||||
if (sha1 == null) return buf;
|
||||
if (!HashUtils.sha1(buf).equals(sha1)) throw new IOException("Invalid hash");
|
||||
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, Class<T> type, boolean cache) throws IOException {
|
||||
return downloadObject(url, () -> HttpUtils.get(url).sendString(), type, cache);
|
||||
}
|
||||
|
||||
public static <T> T downloadObject(String url, Type type) throws IOException {
|
||||
return downloadObject(url, type, 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, Type type, boolean cache) throws IOException {
|
||||
return downloadObject(url, () -> HttpUtils.get(url).sendString(), type, 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 {
|
||||
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();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Could not download object and no cache exists", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String buildUrl(String host, String url, Map<String, String> params) {
|
||||
StringBuilder res = new StringBuilder(host);
|
||||
if (res.toString().endsWith("/")) res = new StringBuilder(res.substring(0, res.length() - 1));
|
||||
if (url.startsWith("/")) res.append(url);
|
||||
else res.append("/").append(url);
|
||||
int i = 0;
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
res.append(i++ == 0 ? '?' : '&')
|
||||
.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
|
||||
.append('=')
|
||||
.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
|
||||
}
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
public static <T> T loadObject(Path file, Class<T> type) throws IOException {
|
||||
try (BufferedReader br = Files.newBufferedReader(file)) {
|
||||
return GsonHolder.getGson().fromJson(br, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T loadObject(Path file, Type type) throws IOException {
|
||||
try (BufferedReader br = Files.newBufferedReader(file)) {
|
||||
return GsonHolder.getGson().fromJson(br, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void writeObject(Path file, T object) throws IOException {
|
||||
try (BufferedWriter bw = Files.newBufferedWriter(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
GsonHolder.getGson().toJson(object, bw);
|
||||
}
|
||||
}
|
||||
|
||||
public static String downloadString(String url, String sha1) throws IOException, URISyntaxException {
|
||||
return new String(downloadData(url, sha1), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
public static void downloadFile(String url, String sha1, Path path) throws IOException, URISyntaxException {
|
||||
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
|
||||
Files.write(path, downloadData(url, sha1));
|
||||
}
|
||||
|
||||
public static void clearDirectory(Path path) throws IOException {
|
||||
clearDirectory(path, p -> true);
|
||||
}
|
||||
|
||||
public static void clearDirectory(Path path, Predicate<Path> shouldDelete) throws IOException {
|
||||
if (!Files.exists(path)) return;
|
||||
try {
|
||||
Utils.ls(path, p -> {
|
||||
if (Files.isDirectory(p)) {
|
||||
try {
|
||||
if (shouldDelete.test(p))
|
||||
deleteRecursive(p, shouldDelete);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
if (shouldDelete.test(p))
|
||||
Files.delete(p);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
throw new IOException("Could not clear directory", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteRecursive(Path path) throws IOException {
|
||||
deleteRecursive(path, p -> true);
|
||||
}
|
||||
|
||||
public static void deleteRecursive(Path path, Predicate<Path> shouldDelete) throws IOException {
|
||||
if (Files.isDirectory(path)) {
|
||||
Files.walkFileTree(path, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
FileVisitResult fv = super.visitFile(file, attrs);
|
||||
if (fv != FileVisitResult.CONTINUE) return fv;
|
||||
if (shouldDelete.test(file))
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
FileVisitResult fv = super.postVisitDirectory(dir, exc);
|
||||
if (fv != FileVisitResult.CONTINUE) return fv;
|
||||
if (shouldDelete.test(dir) && ls(dir).isEmpty()) {
|
||||
Files.delete(dir);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
else Files.delete(path);
|
||||
}
|
||||
|
||||
public static void copyContent(Path source, Path destination) throws IOException {
|
||||
copyContent(source, destination, StandardCopyOption.COPY_ATTRIBUTES);
|
||||
}
|
||||
|
||||
public static void copyContent(Path source, Path destination, CopyOption... copyOptions) throws IOException {
|
||||
if (!Files.exists(destination)) Files.createDirectories(destination);
|
||||
if (Files.isDirectory(source)) {
|
||||
try (Stream<Path> paths = Files.walk(source)) {
|
||||
for (Path path : paths.toList()) {
|
||||
if (source.equals(path)) continue;
|
||||
Path d = destination.resolve(source.relativize(path).toString());
|
||||
if (Files.exists(d)) continue;
|
||||
if (Files.isDirectory(d)) Files.createDirectories(d);
|
||||
else Files.copy(path, d);
|
||||
}
|
||||
}
|
||||
} else if (Files.exists(source)) {
|
||||
Path target = destination.resolve(source.getFileName().toString());
|
||||
if (!Files.exists(target)
|
||||
|| Arrays.asList(copyOptions).contains(StandardCopyOption.REPLACE_EXISTING))
|
||||
Files.copy(source, target, copyOptions);
|
||||
} else {
|
||||
throw new FileNotFoundException(source.toAbsolutePath().toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void openWebBrowser(URI uri) {
|
||||
try {
|
||||
if (OSUtils.TYPE == OSUtils.Type.LINUX && OSUtils.executablePathContains("xdg-open")) {
|
||||
Runtime.getRuntime().exec(new String[]{"xdg-open", uri.toString()});
|
||||
} else if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||
Desktop.getDesktop().browse(uri);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Utils.LOGGER.error("Error opening web browser!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void openFile(File file) {
|
||||
try {
|
||||
if (OSUtils.TYPE == OSUtils.Type.LINUX && OSUtils.executablePathContains("xdg-open")) {
|
||||
Runtime.getRuntime().exec(new String[]{"xdg-open", file.getAbsoluteFile().toString()});
|
||||
} else if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||
Desktop.getDesktop().open(file);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Utils.LOGGER.error("Error opening web browser!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Path> ls(Path dir) throws IOException {
|
||||
try (Stream<Path> sp = Files.list(dir)) {
|
||||
return sp.toList();
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] lsVi(Path dir) throws IOException {
|
||||
try (Stream<Path> sp = Files.list(dir)) {
|
||||
return sp.map(p -> Files.isDirectory(p) ? p.getFileName().toString() + "/" : p.getFileName().toString()).toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Path> ls(Path dir, Predicate<Path> predicate) throws IOException {
|
||||
try (Stream<Path> sp = Files.list(dir); Stream<Path> fi = sp.filter(predicate)) {
|
||||
return fi.toList();
|
||||
}
|
||||
}
|
||||
|
||||
public static <TEx extends Exception> void ls(Path dir, ThrowingConsumer<Path, TEx> consumer) throws IOException, TEx {
|
||||
try (Stream<Path> sp = Files.list(dir)) {
|
||||
for (Path path : sp.toList()) consumer.accept(path);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<Path, MultiAccessFileSystem> zipFsCache = new HashMap<>();
|
||||
public static FileSystem openZipFile(Path zip, boolean create) throws IOException, URISyntaxException {
|
||||
synchronized (zipFsCache) {
|
||||
if (!zipFsCache.containsKey(zip) || zipFsCache.get(zip).isClosed()) {
|
||||
URI fileUri = zip.toUri();
|
||||
zipFsCache.put(zip, MultiAccessFileSystem.create(new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null), create ? Map.of("create", "true") : Map.of(), SYSTEM_LOADER));
|
||||
}
|
||||
return zipFsCache.get(zip).createLens();
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSystemLoader(ClassLoader loader) {
|
||||
SYSTEM_LOADER = loader;
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.util.api;
|
||||
|
||||
import io.gitlab.jfronny.commons.HttpUtils;
|
||||
import io.gitlab.jfronny.gson.reflect.TypeToken;
|
||||
import io.gitlab.jfronny.inceptum.model.gitlab.GitlabJob;
|
||||
import io.gitlab.jfronny.inceptum.model.gitlab.GitlabPackage;
|
||||
import io.gitlab.jfronny.inceptum.model.gitlab.GitlabPackageFile;
|
||||
import io.gitlab.jfronny.inceptum.model.gitlab.GitlabProject;
|
||||
|
||||
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/";
|
||||
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,55 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.util.cache;
|
||||
|
||||
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class GsonFileCache {
|
||||
private final ConcurrentHashMap<String, Object> container = new ConcurrentHashMap<>();
|
||||
private final Path cacheDir;
|
||||
|
||||
public GsonFileCache(Path cacheDir) {
|
||||
this.cacheDir = cacheDir;
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
container.remove(key);
|
||||
try {
|
||||
Files.delete(cacheDir.resolve(key));
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not remove cache entry", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
container.clear();
|
||||
try {
|
||||
Utils.ls(cacheDir, Files::delete);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not clear cache backend", e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T, TEx extends Throwable> T get(String key, ThrowingSupplier<T, TEx> builder, Type klazz) throws TEx {
|
||||
if (!container.containsKey(key)) {
|
||||
Path cd = cacheDir.resolve(key);
|
||||
if (Files.exists(cd)) {
|
||||
try {
|
||||
T value = Utils.loadObject(cd, klazz);
|
||||
Utils.writeObject(cd, value);
|
||||
container.put(key, value);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not read cache", e);
|
||||
}
|
||||
}
|
||||
else container.put(key, builder.get());
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (T) container.get(key);
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.util.cache;
|
||||
|
||||
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class MemoryCache<TKey, TValue> {
|
||||
private final ConcurrentHashMap<TKey, TValue> container = new ConcurrentHashMap<>();
|
||||
|
||||
public void remove(TKey key) {
|
||||
container.remove(key);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
container.clear();
|
||||
}
|
||||
|
||||
public void write() {
|
||||
}
|
||||
|
||||
public <TEx extends Throwable> TValue get(TKey key, ThrowingSupplier<TValue, TEx> builder) throws TEx {
|
||||
if (!container.containsKey(key)) container.put(key, builder.get());
|
||||
return container.get(key);
|
||||
}
|
||||
}
|
|
@ -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,26 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false">
|
||||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<pattern>%d{HH:mm:ss.SSS} %boldCyan(%thread) %boldGreen(%logger{0}) %highlight(%level) %msg%n</pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>inceptum.log</file>
|
||||
<append>false</append>
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<pattern>%d{HH:mm:ss.SSS} %thread %logger{0} %level %msg%n</pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<appender name="MEMORY" class="io.gitlab.jfronny.inceptum.util.MapAppender">
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
<appender-ref ref="MEMORY" />
|
||||
</root>
|
||||
</configuration>
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"version": "${version}",
|
||||
"flavor": "${flavor}",
|
||||
"isPublic": ${isPublic},
|
||||
"isRelease": ${isRelease},
|
||||
"jvm": ${jvm}
|
||||
}
|
|
@ -1,35 +1,31 @@
|
|||
# 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.
|
||||
Launching is performed through a custom ClassLoader and the wrapper command is prepended to the arguments provided by the user.
|
||||
|
||||
## The "wrapper" command
|
||||
Inceptum internally uses this command to inform the main Inceptum jar that it was launched through the Inceptum Wrapper and should allow installing updates.
|
||||
It sets an internal flag and launches the command provided in its arguments.
|
||||
Manually invoking this command WILL cause problems, so a check has been implemented that should prevent this from starting Inceptum in unintended environments.
|
||||
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 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
|
||||
|
||||
This command will go through every line in the file provided in its arguments and executes the command written there.
|
||||
It is intended to be used in scripts that only want to run inceptum once, such as the systemd unit in the AUR package.
|
||||
The batch command has the additional advantage of allowing caches to stay loaded between commands, making execution faster.
|
||||
For example,
|
||||
|
||||
```
|
||||
git pull icesrv
|
||||
mod update all icesrv
|
||||
run server restart icesrv
|
||||
```
|
||||
is equivalent to the bash script
|
||||
```shell
|
||||
#!/bin/sh
|
||||
inceptum git pull icesrv
|
||||
inceptum run server restart icesrv
|
||||
run server restart server2
|
||||
```
|
||||
|
||||
## The "git" command
|
||||
The git command does NOT mirror the behavior of the actual git binary.
|
||||
It merely provides subcommands for running common actions such as cloning, pulling, committing and pushing on instances using the included JGit
|
||||
and performs additional sanitization.
|
||||
Using actual git commands is recommended when the commands provided are not enough for a particular purpose.
|
||||
is equivalent to the bash script
|
||||
|
||||
```shell
|
||||
#!/bin/sh
|
||||
inceptum mod update all icesrv
|
||||
inceptum run server restart icesrv
|
||||
inceptum run server restart server2
|
||||
```
|
||||
|
|
|
@ -1,61 +1,44 @@
|
|||
# File Formats
|
||||
|
||||
Inceptum uses several json formats to store metadata and configs.
|
||||
All of these are subject to change, though automatic migrations will likely be provided.
|
||||
|
||||
## inceptum.json (Main Config)
|
||||
```json
|
||||
|
||||
```json5
|
||||
{
|
||||
// Whether to show snapshots in the version selector for new instances
|
||||
"snapshots": false,
|
||||
// Whether to launch the GUI in dark mode.
|
||||
// Whether to launch the GUI in dark mode
|
||||
// Configurable in Settings->Dark Theme
|
||||
"darkTheme": true,
|
||||
// Whether to require an account to launch the game.
|
||||
// Whether to require an account to launch the game
|
||||
// Intended to allow running the game from USB sticks on constrained networks
|
||||
"enforceAccount": false,
|
||||
// The currently selected account.
|
||||
// The currently selected account
|
||||
// Used to launch the game
|
||||
"lastAccount": "some UUID",
|
||||
// The update channel. Either "CI" or "Stable".
|
||||
// The last name used for an offline session
|
||||
"offlineAccountLastName": "some name",
|
||||
// The update channel. Either "CI" or "Stable"
|
||||
// I personally recommend the CI channel as it gets the latest fixes and features quicker
|
||||
"channel": "CI",
|
||||
// Configuration for the git integration.
|
||||
// This is also used during instance creation, but you can pretty much ignore it if you use an external client or don't create modpacks
|
||||
"git": {
|
||||
// Authentication for git
|
||||
// Used when pushing or pulling from remote repos
|
||||
"instanceAuths": {
|
||||
// The name of an instance. This is equal to its directory name
|
||||
"someInstance": {
|
||||
// The username to use for authentication
|
||||
"username": "yourusername@gmail.com",
|
||||
// The password to use for authentication
|
||||
"password": "SomePassword"
|
||||
},
|
||||
"someOtherInstance": {
|
||||
"username": "yourusername@gmail.com",
|
||||
"password": "SomePassword"
|
||||
}
|
||||
},
|
||||
// The username to use when creating commits
|
||||
"commitUsername": "Inceptum",
|
||||
// The E-Mail address to use when creating commits. This default address doesn't exist btw
|
||||
"commitMail": "inceptum@jfronny.gitlab.io",
|
||||
// Whether to sign commits.
|
||||
// If you want this for additional security, don't use the git integration
|
||||
"signCommits": false
|
||||
}
|
||||
// The author name to add to packs where the metadata format requires specifying one
|
||||
"authorName": "Inceptum"
|
||||
}
|
||||
```
|
||||
|
||||
## accounts.json
|
||||
|
||||
Do not EVER use this file manually!
|
||||
NEVER upload it anywhere!
|
||||
It stores your minecraft account login and CAN BE USED TO IMPERSONATE YOU!
|
||||
|
||||
## instance.json (Instance Metadata)
|
||||
|
||||
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")
|
||||
|
@ -90,7 +73,8 @@ 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": [
|
||||
|
@ -137,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
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,48 +1,53 @@
|
|||
# Installing
|
||||
|
||||
Inceptum can be installed in a number of ways, all of which are documented below.
|
||||
|
||||
## Simple installation
|
||||
|
||||
First, download the Inceptum build appropriate for your system:
|
||||
|
||||
- [Windows x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/latest-windows.jar?job=build_test)
|
||||
- [MacOS x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/latest-macos.jar?job=build_test) (untested)
|
||||
- [Linux x86_64](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/latest-linux.jar?job=build_test)
|
||||
- [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)
|
||||
|
||||
The following additional builds are also available but not recommended for normal use:
|
||||
|
||||
- [Windows/Mac/Linux x86_64 in one](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/latest.jar?job=build_test)
|
||||
- [Headless](https://gitlab.com/JFronny/inceptum/-/jobs/artifacts/master/raw/latest-nogui.jar?job=build_test)
|
||||
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/).
|
||||
Inceptum will use your config directory ($XDG_CONFIG_HOME, ~/.config/Inceptum, %APPDATA%\Inceptum or ~/Library/Application Support/Inceptum) for saving instances/caches/etc.
|
||||
If you want them somewhere else, read on
|
||||
|
||||
### Config files in current directory
|
||||
|
||||
If the Inceptum jar detects a directory called "run" in the directory it is placed in,
|
||||
it will use that instead of the users config directory.
|
||||
|
||||
Create a directory (= Folder) next to the inceptum jar.
|
||||
|
||||
### Config files in a custom location
|
||||
|
||||
You may specify the java VM parameter `-Dinceptum.base=$DIRECTORY` to use a custom directory for Inceptum.
|
||||
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_test)
|
||||
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_test)
|
||||
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
|
||||
|
||||
## Installation from the AUR
|
||||
|
||||
Inceptum is available in the AUR as [`inceptum-git`](https://aur.archlinux.org/packages/inceptum-git)
|
||||
If you use arch linux or a derivative, you may use this package instead of a manual installation.
|
||||
It also includes a template systemd service which you can copy and customize to launch your minecraft server.
|
||||
For more information on the syntax of this services config file, go [here](Commands.md)
|
||||
|
||||
## 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,35 @@
|
|||
# Modules
|
||||
Inceptum is split into multiple separate but interdependent modules.
|
||||
The purpose of this page is to list them, explain their purpose and where to get them
|
||||
|
||||
## common
|
||||
This module contains common, platform-agnostic code shared between the launcher and the wrapper.
|
||||
It can be obtained through the maven.
|
||||
|
||||
## launcher
|
||||
This module contains common, platform-agnostic code between all frontends of the launcher.
|
||||
It can be obtained through the maven.
|
||||
|
||||
## launcher-cli
|
||||
This module contains the platform-agnostic command-line interface for the launcher
|
||||
It can be obtained through the maven or the shadowed Inceptum jar
|
||||
|
||||
## launcher-imgui
|
||||
This module contains a dear-imgui-based frontend for Inceptum.
|
||||
Builds of this module are platform-specific and dependents must manually ensure the correct imgui and lwjgl natives are imported.
|
||||
A build without natives can be obtained through the maven and a build with natives through the shadowed Inceptum jar
|
||||
|
||||
## launcher-dist/Inceptum
|
||||
This module builds a shadowed jar of launcher-cli and launcher-imgui to be used by normal users.
|
||||
It also adds additional, platform-specific commands to the CLI.
|
||||
A shadowed build can be obtained as "Inceptum" from maven, a build with dependencies as "launcher-dist"
|
||||
Windows users can also obtain a binary built using fabric-installer-native-bootstrap.
|
||||
|
||||
## launchwrapper
|
||||
This module is added to the minecraft classpath and therefore independent of any other modules.
|
||||
It handles loading forceload natives
|
||||
|
||||
## wrapper
|
||||
This module serves the purpose of downloading the components necessary for executing Inceptum on the current platform.
|
||||
A build with shadowed dependencies can be obtained through the maven (with the suffix "all") or as a jar.
|
||||
Windows users can also obtain a binary built using fabric-installer-native-bootstrap.
|
|
@ -1,4 +1,5 @@
|
|||
# About
|
||||
|
||||
Inceptum is advanced FOSS Launcher for Minecraft written in Java
|
||||
|
||||
Features:
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# Summary
|
||||
|
||||
- [About](./README.md)
|
||||
- [Installing](./Installing.md)
|
||||
- [CLI](./Commands.md)
|
||||
- [Syncing instances](./Syncing.md)
|
||||
- [File Formats](./FileFormats.md)
|
||||
- [Modules](./Modules.md)
|
|
@ -1,14 +1,17 @@
|
|||
# Syncing instances
|
||||
|
||||
Inceptum supports syncing repositories between instances through git.
|
||||
This is roughly comparable to updatable modpacks in other launchers.
|
||||
There are two workflows for this, you can choose one based on your use case.
|
||||
|
||||
## Uploading a local pack to a game server
|
||||
|
||||
This approach assumes that your intention is to have a game server running Inceptum
|
||||
and to regularly push updates built and tested in an Inceptum instance on your PC.
|
||||
To set this up, initialize a repo on your server and clone it locally
|
||||
|
||||
Server:
|
||||
|
||||
```shell
|
||||
cd /srv/inceptum/instances
|
||||
mkdir icesrv
|
||||
|
@ -16,7 +19,9 @@ cd icesrv
|
|||
git init
|
||||
git config receive.denyCurrentBranch ignore
|
||||
```
|
||||
|
||||
Client:
|
||||
|
||||
```shell
|
||||
git clone inceptum@example.com:/srv/inceptum/instances/icesrv
|
||||
cd icesrv
|
||||
|
@ -26,7 +31,9 @@ git add .
|
|||
git commit -m "Initial commit"
|
||||
git push
|
||||
```
|
||||
|
||||
After that, you can use a script similar to the following to push changes:
|
||||
|
||||
```shell
|
||||
git push -u origin master
|
||||
rconc icesrv stop
|
||||
|
@ -34,10 +41,12 @@ sleep 1
|
|||
ssh -t inceptum@example.com "cd /srv/inceptum/instances/icesrv; git reset --hard"
|
||||
ssh -t admin@example.com "sudo systemctl restart inceptum-icesrv"
|
||||
```
|
||||
|
||||
icesrv is the name of the instance in this example and inceptum-icesrv is a systemd service based on the one included
|
||||
in the AUR package.
|
||||
|
||||
## Providing a pack to multiple users
|
||||
|
||||
To do this, create a new repository on a git hosting site (like GitHub or GitLab) and push your local instance there.
|
||||
Since a git repository is created for every instance, simply following the instructions for pushing an existing
|
||||
repository or using any graphical tool is enough to set this up.
|
||||
|
@ -45,5 +54,5 @@ repository or using any graphical tool is enough to set this up.
|
|||
Users can add an instance created this way in the GUI under File->New Instance->Inceptum.
|
||||
|
||||
To update the pack, simply create a new commit and push it to your repository.
|
||||
Users can then update the pack under Edit->Git->Pull or using the Inceptum CLI.
|
||||
Users can then update the pack by pulling the changes into their instance directory through the normal git CLI.
|
||||
You may also export the pack to other formats to upload it on sites like CurseForge
|
|
@ -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"]
|
|
@ -0,0 +1,11 @@
|
|||
plugins {
|
||||
inceptum.application
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("io.gitlab.jfronny.inceptum.cli.CliMain")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.launcher)
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package io.gitlab.jfronny.inceptum.cli;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.common.MetaHolder;
|
||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||
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;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class BaseInstanceCommand extends Command {
|
||||
protected BaseInstanceCommand(String help, String usage, List<String> aliases, List<Command> subCommands) {
|
||||
super(help, mutateUsage(usage), aliases, subCommands);
|
||||
}
|
||||
|
||||
private static String mutateUsage(String usage) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String s : usage.split("\n")) {
|
||||
if (s.isBlank())
|
||||
sb.append("\n<instance>");
|
||||
else
|
||||
sb.append("\n<instance> ").append(s);
|
||||
}
|
||||
return sb.substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(CommandArgs args) throws Exception {
|
||||
if (args.length == 0) {
|
||||
Utils.LOGGER.error("You must specify an instance to commit in");
|
||||
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, Instance instance) throws Exception;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package io.gitlab.jfronny.inceptum.cli;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
|
||||
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Scanner;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class CliEnvBackend implements LauncherEnv.EnvBackend {
|
||||
@Override
|
||||
public void showError(String message, String title) {
|
||||
Utils.LOGGER.error(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showError(String message, Throwable t) {
|
||||
Utils.LOGGER.error(message, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showInfo(String message, String title) {
|
||||
Utils.LOGGER.info(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showOkCancel(String message, String title, Runnable ok, Runnable cancel, boolean defaultCancel) {
|
||||
Utils.LOGGER.info(message);
|
||||
if (!defaultCancel) ok.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getInput(String prompt, String details, String defaultValue, Consumer<String> ok, Runnable cancel) throws IOException {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
System.out.println(prompt);
|
||||
System.out.print("> ");
|
||||
ok.accept(scanner.nextLine());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLoginRefreshPrompt(MicrosoftAccount account) {
|
||||
Utils.LOGGER.error("Could not launch using the selected account as its login has expired. Please log in again through the UI!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package io.gitlab.jfronny.inceptum.cli;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.cli.commands.*;
|
||||
import io.gitlab.jfronny.inceptum.common.*;
|
||||
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class CliMain {
|
||||
public static Command DEFAULT = new HelpCommand();
|
||||
public static final List<Command> KNOWN_COMMANDS = new LinkedList<>(List.of(
|
||||
DEFAULT,
|
||||
new LaunchCommand(),
|
||||
new ListCommand(),
|
||||
new ModCommand(),
|
||||
new ImportCommand(),
|
||||
new ExportCommand(),
|
||||
new JvmStateCommand(),
|
||||
new BatchCommand()
|
||||
));
|
||||
|
||||
public static final Command COMMANDS_ROOT = new Command("Root command", "<command>", List.of(), KNOWN_COMMANDS) {
|
||||
@Override
|
||||
protected void invoke(CommandArgs args) {
|
||||
throw new RuntimeException("Could not find command: " + args.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandResolution resolve(CommandArgs args) {
|
||||
if (args.length == 0) return new CommandResolution(DEFAULT, args, new ArrayList<>());
|
||||
return super.resolve(args);
|
||||
}
|
||||
};
|
||||
|
||||
public static CommandResolution resolve(String[] args) {
|
||||
return COMMANDS_ROOT.resolve(new CommandArgs(Arrays.asList(args)));
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
LauncherEnv.initialize(new CliEnvBackend());
|
||||
|
||||
CommandResolution command = resolve(args);
|
||||
if (command.command().enableLog()) {
|
||||
Utils.LOGGER.info("Launching Inceptum v" + BuildMetadata.VERSION);
|
||||
Utils.LOGGER.info("Loading from " + MetaHolder.BASE_PATH);
|
||||
}
|
||||
|
||||
try {
|
||||
command.invoke();
|
||||
} catch (Exception e) {
|
||||
Utils.LOGGER.error("Could not execute command", e);
|
||||
} finally {
|
||||
LauncherEnv.terminate();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.inceptum.frontend.cli;
|
||||
package io.gitlab.jfronny.inceptum.cli;
|
||||
|
||||
import java.util.*;
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
package io.gitlab.jfronny.inceptum.frontend.cli;
|
||||
package io.gitlab.jfronny.inceptum.cli;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class CommandArgs implements Iterable<String> {
|
||||
protected final List<String> args;
|
||||
public final int length;
|
||||
public final boolean wrapped;
|
||||
|
||||
public CommandArgs(List<String> args, boolean wrapped) {
|
||||
public CommandArgs(List<String> args) {
|
||||
this.args = List.copyOf(args);
|
||||
this.length = args.size();
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
public boolean contains(String param) {
|
||||
|
@ -50,11 +48,7 @@ public class CommandArgs implements Iterable<String> {
|
|||
}
|
||||
|
||||
public CommandArgs subArgs() {
|
||||
return subArgs(wrapped);
|
||||
}
|
||||
|
||||
public CommandArgs subArgs(boolean wrapped) {
|
||||
return new CommandArgs(args.subList(1, args.size()), wrapped);
|
||||
return new CommandArgs(args.subList(1, args.size()));
|
||||
}
|
||||
|
||||
@Override
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.inceptum.frontend.cli;
|
||||
package io.gitlab.jfronny.inceptum.cli;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.inceptum.frontend.cli;
|
||||
package io.gitlab.jfronny.inceptum.cli;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -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,15 +1,10 @@
|
|||
package io.gitlab.jfronny.inceptum.frontend.cli.commands;
|
||||
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||
|
||||
import io.gitlab.jfronny.commons.ArgumentsTokenizer;
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.Command;
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.CommandArgs;
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.CommandResolution;
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.Commands;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
import io.gitlab.jfronny.inceptum.cli.*;
|
||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.*;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -37,9 +32,7 @@ public class BatchCommand extends Command {
|
|||
}
|
||||
try {
|
||||
for (String line : Files.readAllLines(p)) {
|
||||
CommandResolution resolution = Commands.resolve(ArgumentsTokenizer.tokenize(line), args.wrapped);
|
||||
if (resolution.command() instanceof WrapperCommand)
|
||||
throw new Exception("You may not use the wrapper command in a command batch");
|
||||
CommandResolution resolution = CliMain.resolve(ArgumentsTokenizer.tokenize(line));
|
||||
resolved.add(resolution);
|
||||
}
|
||||
} catch (Exception e) {
|
|
@ -0,0 +1,58 @@
|
|||
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.cli.*;
|
||||
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.Paths;
|
||||
import java.util.List;
|
||||
|
||||
public class ExportCommand extends BaseInstanceCommand {
|
||||
public ExportCommand() {
|
||||
this(List.of("export"), List.of(
|
||||
new ExportCommand(List.of("curseforge", "cf"), List.of()),
|
||||
new ModrinthExportCommand(List.of("modrinth", "mr"), List.of()),
|
||||
new MultiMCExportCommand(List.of("multimc", "mmc"), List.of())
|
||||
));
|
||||
}
|
||||
|
||||
private ExportCommand(List<String> aliases, List<Command> subCommands) {
|
||||
super("Export a CurseForge instance", "<export path> <version>", aliases, subCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
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 > 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 {
|
||||
public MultiMCExportCommand(List<String> aliases, List<Command> subCommands) {
|
||||
super("Export a MultiMC instance", "<export path>", aliases, subCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
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");
|
||||
Exporters.MULTI_MC.generate(new ProcessState(), instance, Paths.get(args.get(0)));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ModrinthExportCommand extends BaseInstanceCommand {
|
||||
public ModrinthExportCommand(List<String> aliases, List<Command> subCommands) {
|
||||
super("Export a Modrinth instance", "<export path> <version>", aliases, subCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
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 > 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)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package io.gitlab.jfronny.inceptum.frontend.cli.commands;
|
||||
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.*;
|
||||
import io.gitlab.jfronny.inceptum.util.MetaHolder;
|
||||
import io.gitlab.jfronny.inceptum.cli.*;
|
||||
import io.gitlab.jfronny.inceptum.common.BuildMetadata;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -9,19 +9,19 @@ public class HelpCommand extends Command {
|
|||
public HelpCommand() {
|
||||
super("Displays this screen", "[command]", "help");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(CommandArgs args) {
|
||||
HelpBuilder help = new HelpBuilder();
|
||||
if (args.length == 0) {
|
||||
printHeader();
|
||||
System.out.println("\nCommands:");
|
||||
Commands.COMMANDS_ROOT.buildHelp(help);
|
||||
}
|
||||
else {
|
||||
CommandResolution resolution = Commands.COMMANDS_ROOT.resolve(args);
|
||||
CliMain.COMMANDS_ROOT.buildHelp(help);
|
||||
} else {
|
||||
CommandResolution resolution = CliMain.COMMANDS_ROOT.resolve(args);
|
||||
if (resolution.resolvePath().isEmpty()) {
|
||||
System.err.println("Could not find command matching your input");
|
||||
invoke(new CommandArgs(List.of(), args.wrapped));
|
||||
invoke(new CommandArgs(List.of()));
|
||||
return;
|
||||
}
|
||||
printHeader();
|
||||
|
@ -32,6 +32,6 @@ public class HelpCommand extends Command {
|
|||
}
|
||||
|
||||
private static void printHeader() {
|
||||
System.out.println("Inceptum v" + MetaHolder.VERSION.version + " (" + MetaHolder.VERSION.flavor + ")");
|
||||
System.out.println("Inceptum v" + BuildMetadata.VERSION);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package io.gitlab.jfronny.inceptum.frontend.cli.commands;
|
||||
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.Command;
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.CommandArgs;
|
||||
import io.gitlab.jfronny.inceptum.cli.Command;
|
||||
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
|
||||
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Arrays;
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
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.api.account.AccountManager;
|
||||
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.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class LaunchCommand extends BaseInstanceCommand {
|
||||
private final boolean server;
|
||||
private final boolean restart;
|
||||
|
||||
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", "instance.launch", "start"),
|
||||
List.of(
|
||||
new LaunchCommand("Explicitly launch a client", "client", false, false),
|
||||
new LaunchCommand("Launch a server", "server", true, false,
|
||||
new LaunchCommand("Restart the server if it stops", "restart", true, true)
|
||||
)
|
||||
));
|
||||
this.server = false;
|
||||
this.restart = false;
|
||||
}
|
||||
|
||||
private LaunchCommand(String help, String name, boolean server, boolean restart, Command... subCommands) {
|
||||
super(help, "[game arguments...]", List.of(name), Arrays.asList(subCommands));
|
||||
this.server = server;
|
||||
this.restart = restart;
|
||||
}
|
||||
|
||||
@Override
|
||||
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 (instance.isRunningLocked()) {
|
||||
Utils.LOGGER.error("This instance is already running");
|
||||
return;
|
||||
}
|
||||
if (args.length > 1) {
|
||||
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(instance, Steps.createProcessState());
|
||||
if (server) {
|
||||
InstanceLauncher.launch(instance, LaunchType.Server, restart, AccountManager.NULL_AUTH);
|
||||
} else {
|
||||
AccountManager.loadAccounts();
|
||||
InstanceLauncher.launchClient(instance);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||
|
||||
import io.gitlab.jfronny.commons.io.JFiles;
|
||||
import io.gitlab.jfronny.inceptum.cli.Command;
|
||||
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
|
||||
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;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public class ListCommand extends Command {
|
||||
public ListCommand() {
|
||||
super("Lists all available instances", "", "list", "ls");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(CommandArgs args) throws IOException {
|
||||
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.CONFIG_NAME))) {
|
||||
System.out.println("- Invalid instance: " + path + " (no instance metadata)");
|
||||
continue;
|
||||
}
|
||||
System.out.println("- \"" + path.getFileName().toString() + "\"");
|
||||
Instance instance;
|
||||
try {
|
||||
instance = InstanceList.read(path);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error(" Could not load instance.json", e);
|
||||
continue;
|
||||
}
|
||||
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.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 : ""));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,13 @@
|
|||
package io.gitlab.jfronny.inceptum.frontend.cli.commands;
|
||||
package io.gitlab.jfronny.inceptum.cli.commands;
|
||||
|
||||
import io.gitlab.jfronny.commons.io.JFiles;
|
||||
import io.gitlab.jfronny.commons.throwable.ThrowingBiFunction;
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.BaseInstanceCommand;
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.Command;
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.CommandArgs;
|
||||
import io.gitlab.jfronny.inceptum.frontend.gui.window.AddModWindow;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.util.ModManager;
|
||||
import io.gitlab.jfronny.inceptum.util.PathUtil;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.IWModDescription;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.source.ModSource;
|
||||
import io.gitlab.jfronny.inceptum.cli.*;
|
||||
import io.gitlab.jfronny.inceptum.common.Utils;
|
||||
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.util.Unchecked;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
@ -49,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());
|
||||
|
@ -67,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());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -80,6 +74,7 @@ public class ModCommand extends Command {
|
|||
|
||||
public static class ModRemoveCommand extends BaseInstanceCommand {
|
||||
private final boolean ignoreDependencies;
|
||||
|
||||
public ModRemoveCommand() {
|
||||
this("Removes mods from an instance", List.of("remove", "delete", "rm", "del"), List.of(
|
||||
new ModRemoveCommand("Skip dependency checks", List.of("ignore-dependencies"), List.of(), true)
|
||||
|
@ -92,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;
|
||||
}
|
||||
|
@ -103,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;
|
||||
|
@ -132,7 +127,7 @@ public class ModCommand extends Command {
|
|||
|
||||
public ModUpdateCommand() {
|
||||
this("Update mods in an instance", "<mod file>...", List.of("update", "upgrade", "up"), List.of(
|
||||
new ModUpdateCommand("Update all mods in an instance", "", List.of("all"), List.of(), (args, instancePath) -> Set.copyOf(Utils.ls(instancePath)))
|
||||
new ModUpdateCommand("Update all mods in an instance", "", List.of("all"), List.of(), (args, instancePath) -> Set.copyOf(JFiles.list(instancePath)))
|
||||
), (args, instancePath) -> {
|
||||
Set<Path> mods = new HashSet<>();
|
||||
for (String arg : args) {
|
||||
|
@ -153,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() + PathUtil.EXT_IMOD);
|
||||
AddModWindow.DownloadMeta dm = AddModWindow.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;
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
import io.gitlab.jfronny.scripts.*
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
plugins {
|
||||
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(projects.launcher)
|
||||
implementation(projects.launcherCli)
|
||||
implementation(projects.launcherImgui)
|
||||
}
|
||||
|
||||
tasks.shadowJar {
|
||||
archiveClassifier.set(rootProject.extra["flavorProp"] as String)
|
||||
archiveBaseName.set("Inceptum")
|
||||
exclude("about.html")
|
||||
exclude("plugin.properties")
|
||||
exclude("META-INF/**")
|
||||
}
|
||||
|
||||
(components["java"] as AdhocComponentWithVariants).withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
|
||||
skip()
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
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"
|
||||
))
|
||||
}
|
||||
"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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package io.gitlab.jfronny.inceptum;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.cli.Command;
|
||||
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
|
||||
import io.gitlab.jfronny.inceptum.imgui.GuiEnvBackend;
|
||||
import io.gitlab.jfronny.inceptum.imgui.GuiMain;
|
||||
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
|
||||
|
||||
public class GuiCommand extends Command {
|
||||
public GuiCommand() {
|
||||
super("Displays the Inceptum UI", "", "gui", "show");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(CommandArgs args) {
|
||||
LauncherEnv.updateBackend(new GuiEnvBackend());
|
||||
GuiMain.showGui();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableLog() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package io.gitlab.jfronny.inceptum;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.cli.CliMain;
|
||||
|
||||
public class Inceptum {
|
||||
private static final GuiCommand GUI_COMMAND = new GuiCommand();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
CliMain.KNOWN_COMMANDS.add(1, GUI_COMMAND);
|
||||
CliMain.KNOWN_COMMANDS.add(new UpdateCheckCommand());
|
||||
CliMain.DEFAULT = GUI_COMMAND;
|
||||
CliMain.main(args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package io.gitlab.jfronny.inceptum;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.cli.Command;
|
||||
import io.gitlab.jfronny.inceptum.cli.CommandArgs;
|
||||
import io.gitlab.jfronny.inceptum.common.*;
|
||||
import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateMetadata;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
public class UpdateCheckCommand extends Command {
|
||||
private final boolean install;
|
||||
|
||||
public UpdateCheckCommand() {
|
||||
super("Checks for inceptum updates", "", List.of("update"), List.of(
|
||||
new UpdateCheckCommand("Automatically install updates", "install", true)
|
||||
));
|
||||
install = false;
|
||||
}
|
||||
|
||||
private UpdateCheckCommand(String help, String name, boolean install) {
|
||||
super(help, "", name);
|
||||
this.install = install;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(CommandArgs args) {
|
||||
if (install && !MetaHolder.isWrapper()) {
|
||||
Utils.LOGGER.error("Automatic updates are not supported without the wrapper");
|
||||
return;
|
||||
}
|
||||
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 " + update);
|
||||
try {
|
||||
Updater.update(update, false);
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
Utils.LOGGER.error("Could not download update", e);
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue