Compare commits
37 Commits
1.0.2+1.16
...
master
Author | SHA1 | Date | |
---|---|---|---|
0bc2eb8b07 | |||
37fe78675e | |||
64d94e4d68 | |||
|
a58350f0ce | ||
0a2afee6ee | |||
47ac38726f | |||
|
bed078867d | ||
1d4494d905 | |||
2f140c4a60 | |||
4d4facdf50 | |||
74c97537b6 | |||
fedd2fe2d8 | |||
7b43cf36bb | |||
4f6bc2c108 | |||
c3c2b47b67 | |||
9c9f759ab5 | |||
bafa2723c2 | |||
b78675db11 | |||
ff407b1c74 | |||
c0303bf44f | |||
26073170b7 | |||
897cec88d3 | |||
1a48e714b0 | |||
e5ca6e5e7b | |||
e4b15f4dd9 | |||
f86a6f5967 | |||
6e6713e2fd | |||
330e16d469 | |||
ca6e72d00c | |||
4199aa02bb | |||
c3c45a033e | |||
bc36e1d390 | |||
88a6aa28fb | |||
d6420cdf5f | |||
0a941684f8 | |||
17f56fd400 | |||
36730cccbc |
68
CHANGELOG.md
68
CHANGELOG.md
|
@ -1,5 +1,73 @@
|
|||
# Changelog
|
||||
|
||||
**1.1.11**
|
||||
* Add Translations
|
||||
|
||||
**1.1.10**
|
||||
* Improve
|
||||
|
||||
**1.1.9**
|
||||
* Add ```modupdater``` Entry-Point
|
||||
|
||||
**1.1.8**
|
||||
* Strict Minecraft Version Checking by Default
|
||||
* Print Message To Log Showing All Scanned Mods
|
||||
|
||||
**1.1.7**
|
||||
* Disable SemVer In JSON Strategy
|
||||
|
||||
**1.1.6**
|
||||
* Drop Dom4j Dependency
|
||||
|
||||
**1.1.5**
|
||||
* Add JSON Strategy
|
||||
|
||||
**1.1.4**
|
||||
* Add Refresh Button To GUI
|
||||
* Add ``/modupdater`` Command
|
||||
|
||||
**1.1.3**
|
||||
* Swap Buttons In GUI
|
||||
|
||||
**1.1.2**
|
||||
* Cache Reader Objects
|
||||
* Improve Logging
|
||||
|
||||
**1.1.1**
|
||||
* Use CurseForge For Fabric API
|
||||
|
||||
**1.1.0**
|
||||
* Stabilize
|
||||
* Improve Error Messages
|
||||
|
||||
**1.0.11**
|
||||
* Close Input Stream Properly
|
||||
|
||||
**1.0.10**
|
||||
* Fix Fabric API Update Detection
|
||||
|
||||
**1.0.9**
|
||||
* Fix Transitive Dependencies
|
||||
|
||||
**1.0.8**
|
||||
* Remove Debug Code
|
||||
|
||||
**1.0.7**
|
||||
* Fix Loading Maven XML
|
||||
|
||||
**1.0.6**
|
||||
* Fix Crash (Again)
|
||||
|
||||
**1.0.5**
|
||||
* Fix Crash
|
||||
|
||||
**1.0.4**
|
||||
* Improve File Compatibility Check
|
||||
|
||||
**1.0.3**
|
||||
* Add Support For GitHub Releases
|
||||
* Use Multi-Threading
|
||||
|
||||
**1.0.2**
|
||||
* Improve Errors
|
||||
* Improve GUI
|
||||
|
|
13
Jenkinsfile
vendored
13
Jenkinsfile
vendored
|
@ -15,5 +15,18 @@ pipeline {
|
|||
}
|
||||
}
|
||||
}
|
||||
stage('Publish') {
|
||||
when {
|
||||
expression {
|
||||
return sh(returnStdout: true, script: 'git tag --contains').trim().length() > 0
|
||||
}
|
||||
}
|
||||
steps {
|
||||
sh './gradlew publish'
|
||||
withCredentials([string(credentialsId: 'curseforge_key', variable: 'CURSEFORGE_KEY')]) {
|
||||
sh './gradlew -Pcurseforge.api_key="${CURSEFORGE_KEY}" curseforge'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
139
MOD_DEVELOPER.md
Normal file
139
MOD_DEVELOPER.md
Normal file
|
@ -0,0 +1,139 @@
|
|||
# Mod Developers
|
||||
To opt-in a mod for ModUpdater, you must select an update strategy in ```fabric.mod.json```.
|
||||
|
||||
## CurseForge
|
||||
This update strategy uses the CurseForge API to check for updates.
|
||||
|
||||
### ```fabric.mod.json```
|
||||
```json
|
||||
{
|
||||
"custom": {
|
||||
"modupdater": {
|
||||
"strategy": "curseforge",
|
||||
"projectID": 306612
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Requires Semantic Versioning
|
||||
- In Loose Versioning Mode, Can Infer File's Supported Minecraft version From CurseForge Metadata
|
||||
- [Requires ```build.gradle``` modification](#build-gradle-modification)
|
||||
|
||||
## GitHub Releases
|
||||
This update strategy uses the GitHub Releases API to check for updates.
|
||||
|
||||
### ```fabric.mod.json```
|
||||
```json
|
||||
{
|
||||
"custom": {
|
||||
"modupdater": {
|
||||
"strategy": "github",
|
||||
"owner": "Repository Owner",
|
||||
"repository": "Repository Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Requires Semantic Versioning
|
||||
- [Requires ```build.gradle``` Modification](#build-gradle-modification)
|
||||
|
||||
## Maven
|
||||
This update strategy uses the specified Maven repository to check for updates.
|
||||
|
||||
### ```fabric.mod.json```
|
||||
```json
|
||||
{
|
||||
"custom": {
|
||||
"modupdater": {
|
||||
"strategy": "maven",
|
||||
"repository": "https://maven.fabricmc.net",
|
||||
"group": "net.fabricmc.fabric-api",
|
||||
"artifact": "fabric-api"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Requires Semantic Versioning
|
||||
- [Requires ```build.gradle``` Modification](#build-gradle-modification)
|
||||
|
||||
## JSON
|
||||
This update strategy uses the specified JSON file to check for updates.
|
||||
|
||||
### ```fabric.mod.json```
|
||||
```json
|
||||
{
|
||||
"custom": {
|
||||
"modupdater": {
|
||||
"strategy": "json",
|
||||
"url": "https://example.com/thing.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JSON Format
|
||||
```json
|
||||
{
|
||||
"1.16.1": {
|
||||
"version": "1.0.1",
|
||||
"downloadUrl": "https://example.com/thing2.jar"
|
||||
},
|
||||
"20w20a": {
|
||||
"version": "1.0.0",
|
||||
"downloadUrl": "https://example.com/thing.jar"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Does Not Use Semantic Versioning
|
||||
- A mod is marked as out-of-date if the version in the JSON is different from the current version, so if the current version is newer than the one in the JSON, it will still be marked as out-of-date.
|
||||
- ```build.gradle``` Modification Is Not Required
|
||||
|
||||
## ```build.gradle``` Modification
|
||||
Multiple update strategies require the Minecraft version to be appended to the end of the JAR version to detect what Minecraft version a JAR supports.
|
||||
|
||||
Replace:
|
||||
```gradle
|
||||
version = project.mod_version
|
||||
```
|
||||
with:
|
||||
```gradle
|
||||
version = "${project.mod_version}+${project.minecraft_version}"
|
||||
```
|
||||
|
||||
If you prefer hyphens you can also use:
|
||||
```gradle
|
||||
version = "${project.mod_version}-${project.minecraft_version}"
|
||||
```
|
||||
|
||||
## Loose VS Strict Versioning Mode
|
||||
|
||||
### Strict (Default)
|
||||
In strict mode it only marks a file as compatibleif the Minecraft version is identical.
|
||||
|
||||
### Loose
|
||||
```json
|
||||
{
|
||||
"custom": {
|
||||
"modupdater": {
|
||||
"strict": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
In loose mode, it will also mark a file as compatible if it has the same release target.
|
||||
|
||||
## Custom Version Compatibility Checking
|
||||
You can also specify the ```modupdater``` entry-point as a ```ModUpdaterEntryPoint``` to check if a version is compatible with the current MC version.
|
||||
```gradle
|
||||
repositories {
|
||||
maven { url 'https://maven.thebrokenrail.com' }
|
||||
}
|
||||
dependencies {
|
||||
modCompileOnly 'com.thebrokenrail:modupdater:VERSION'
|
||||
// VERSION = "<Mod Version>+<MC Version>", for example "1.2.4+20w12a"
|
||||
}
|
||||
```
|
44
README.md
44
README.md
|
@ -1,51 +1,19 @@
|
|||
# ModUpdater
|
||||
A simple Minecraft mod updater.
|
||||
|
||||
Created For [ModFest 1.16](https://modfest.net/1.16)
|
||||
|
||||
**NOTE:** This is only able to scan mods that have opted-in!
|
||||
|
||||
## Mod Users
|
||||
Go to the Mod Menu and click the configure icon for ModUpdater.
|
||||
Go to the Mod Menu and click the configure icon to show the ModUpdater GUI or use the ```/modupdater``` command.
|
||||
|
||||
## Mod Developers
|
||||
Place this in your ``fabric.mod.json``:
|
||||
|
||||
**Maven**
|
||||
```json
|
||||
{
|
||||
"custom": {
|
||||
"modupdater": {
|
||||
"strategy": "maven",
|
||||
"repository": "https://maven.fabricmc.net",
|
||||
"group": "net.fabricmc.fabric-api",
|
||||
"artifact": "fabric-api"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**CurseForge**
|
||||
```json
|
||||
{
|
||||
"custom": {
|
||||
"modupdater": {
|
||||
"strategy": "curseforge",
|
||||
"projectID": 306612
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Also replace this in ````build.gradle````:
|
||||
```gradle
|
||||
version = project.mod_version
|
||||
```
|
||||
with:
|
||||
```gradle
|
||||
version = "${project.mod_version}+${project.minecraft_version}"
|
||||
```
|
||||
[View Mod Developers](MOD_DEVELOPER.md)
|
||||
|
||||
## Changelog
|
||||
[View Changelog](CHANGELOG.md)
|
||||
|
||||
## Credits
|
||||
The icon was created by ``ProspectorDev``.
|
||||
- The icon was created by ``ProspectorDev``
|
||||
- The GitHub Releases strategy was written by ``AppleTheGolden``
|
41
build.gradle
41
build.gradle
|
@ -1,6 +1,7 @@
|
|||
plugins {
|
||||
id 'fabric-loom' version '0.4-SNAPSHOT'
|
||||
id 'com.matthewprenger.cursegradle' version '1.4.0'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
compileJava {
|
||||
|
@ -8,9 +9,16 @@ compileJava {
|
|||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
version = "${project.mod_version}+${project.minecraft_version}"
|
||||
def mod_version = project.mod_version as Object
|
||||
version = "${mod_version}+${project.minecraft_version}"
|
||||
group = project.maven_group as Object
|
||||
|
||||
configurations {
|
||||
includeTransitive {
|
||||
transitive = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||
mappings "net.fabricmc:yarn:${project.minecraft_version}+build.${project.yarn_build}:v2"
|
||||
|
@ -21,14 +29,22 @@ dependencies {
|
|||
modImplementation "io.github.prospector:modmenu:${project.modmenu_version}"
|
||||
|
||||
implementation "com.squareup.moshi:moshi:${project.moshi_version}"
|
||||
include "com.squareup.moshi:moshi:${project.moshi_version}"
|
||||
|
||||
implementation "org.dom4j:dom4j:${project.dom4j_version}"
|
||||
include "org.dom4j:dom4j:${project.dom4j_version}"
|
||||
includeTransitive "com.squareup.moshi:moshi:${project.moshi_version}"
|
||||
|
||||
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
}
|
||||
|
||||
project.afterEvaluate {
|
||||
configurations.includeTransitive.incoming.resolutionResult.allComponents {
|
||||
if (it.id instanceof ModuleComponentIdentifier) {
|
||||
def that = it
|
||||
dependencies {
|
||||
include group: that.id.getGroup(), name: that.id.getModule(), version: that.id.getVersion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
inputs.property "version", project.version
|
||||
|
||||
|
@ -84,4 +100,19 @@ if (project.hasProperty('curseforge.api_key')) {
|
|||
forgeGradleIntegration = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
artifact(remapJar) {
|
||||
builtBy remapJar
|
||||
}
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
url '/data/maven'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,18 +2,17 @@
|
|||
org.gradle.jvmargs = -Xmx1G
|
||||
|
||||
# Fabric Properties
|
||||
minecraft_version = 1.16
|
||||
minecraft_version = 1.16.2-rc2
|
||||
curseforge_id = 391583
|
||||
simple_minecraft_version = 1.16
|
||||
simple_minecraft_version = 1.16.2
|
||||
yarn_build = 1
|
||||
fabric_loader_version = 0.8.8+build.202
|
||||
fabric_loader_version = 0.9.1+build.205
|
||||
|
||||
# Mod Properties
|
||||
mod_version = 1.0.2
|
||||
mod_version = 1.1.11
|
||||
maven_group = com.thebrokenrail
|
||||
|
||||
# Dependencies
|
||||
fabric_api_version = 0.13.1+build.370-1.16
|
||||
modmenu_version = 1.12.2+build.16
|
||||
fabric_api_version = 0.17.2+build.396-1.16
|
||||
modmenu_version = 1.14.6+build.31
|
||||
moshi_version = 1.9.2
|
||||
dom4j_version = 2.1.3
|
||||
|
|
|
@ -1,39 +1,46 @@
|
|||
package com.thebrokenrail.modupdater;
|
||||
|
||||
import com.thebrokenrail.modupdater.strategy.ModUpdateStrategies;
|
||||
import com.thebrokenrail.modupdater.util.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.command.ModUpdaterCommand;
|
||||
import com.thebrokenrail.modupdater.data.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.strategy.util.UpdateStrategyRunner;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ModUpdater implements ModInitializer {
|
||||
public static final String NAMESPACE = "modupdater";
|
||||
|
||||
private static final String LOGGER_NAME = "ModUpdater";
|
||||
|
||||
public static Logger getLogger() {
|
||||
private static Logger getLogger() {
|
||||
return LogManager.getLogger(LOGGER_NAME);
|
||||
}
|
||||
|
||||
public static void invalidModUpdaterConfig(String modID) {
|
||||
getLogger().warn("Invalid JSON Configuration: " + modID);
|
||||
public static void logWarn(String name, String msg) {
|
||||
getLogger().warn(String.format("%s: %s", name, msg));
|
||||
}
|
||||
|
||||
private static ModUpdate[] updates;
|
||||
public static void logInfo(String info) {
|
||||
getLogger().info(info);
|
||||
}
|
||||
|
||||
private static volatile ModUpdate[] updates = null;
|
||||
|
||||
public static void findUpdates() {
|
||||
updates = null;
|
||||
new Thread(() -> updates = UpdateStrategyRunner.checkAllModsForUpdates()).start();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ModUpdate[] getUpdates() {
|
||||
if (updates == null) {
|
||||
updates = ModUpdateStrategies.findAvailableUpdates();
|
||||
}
|
||||
return updates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
getLogger().info("Checking For Mod Updates...");
|
||||
for (ModUpdate update : getUpdates()) {
|
||||
getLogger().info(update.text + " (" + update.downloadURL + ')');
|
||||
}
|
||||
getLogger().info(updates.length + " Mod Update(s) Found");
|
||||
findUpdates();
|
||||
ModUpdaterCommand.register();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package com.thebrokenrail.modupdater.api;
|
||||
|
||||
public interface ConfigObject {
|
||||
String getString(String str) throws MissingValueException;
|
||||
|
||||
int getInt(String str) throws MissingValueException;
|
||||
|
||||
boolean getBoolean(String str) throws MissingValueException;
|
||||
|
||||
class MissingValueException extends Exception {
|
||||
private static final String MISSING_MSG = "Missing Configuration Property: %s";
|
||||
private static final String INVALID_MSG = "Invalid Configuration Property: %s";
|
||||
|
||||
public MissingValueException(boolean invalid, String property) {
|
||||
super(String.format(invalid ? INVALID_MSG : MISSING_MSG, property));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.thebrokenrail.modupdater.api;
|
||||
|
||||
import com.thebrokenrail.modupdater.data.ModUpdate;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface UpdateStrategy {
|
||||
@Nullable
|
||||
ModUpdate run(ConfigObject obj, String oldVersion, String name, String id);
|
||||
|
||||
default boolean isStrict(ConfigObject obj) {
|
||||
try {
|
||||
return obj.getBoolean("strict");
|
||||
} catch (ConfigObject.MissingValueException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.thebrokenrail.modupdater.api.entrypoint;
|
||||
|
||||
public interface ModUpdaterEntryPoint {
|
||||
boolean isVersionCompatible(String version);
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package com.thebrokenrail.modupdater.api.impl;
|
||||
|
||||
import com.thebrokenrail.modupdater.api.ConfigObject;
|
||||
import net.fabricmc.loader.api.metadata.CustomValue;
|
||||
|
||||
public class ConfigObjectCustom implements ConfigObject {
|
||||
private final CustomValue.CvObject obj;
|
||||
|
||||
public ConfigObjectCustom(CustomValue.CvObject obj) {
|
||||
this.obj = obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String str) throws MissingValueException {
|
||||
if (obj.containsKey(str)) {
|
||||
try {
|
||||
return obj.get(str).getAsString();
|
||||
} catch (ClassCastException e) {
|
||||
throw new MissingValueException(true, str);
|
||||
}
|
||||
} else {
|
||||
throw new MissingValueException(false, str);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String str) throws MissingValueException {
|
||||
if (obj.containsKey(str)) {
|
||||
try {
|
||||
return obj.get(str).getAsNumber().intValue();
|
||||
} catch (ClassCastException e) {
|
||||
throw new MissingValueException(true, str);
|
||||
}
|
||||
} else {
|
||||
throw new MissingValueException(false, str);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String str) throws MissingValueException {
|
||||
if (obj.containsKey(str)) {
|
||||
try {
|
||||
return obj.get(str).getAsBoolean();
|
||||
} catch (ClassCastException e) {
|
||||
throw new MissingValueException(true, str);
|
||||
}
|
||||
} else {
|
||||
throw new MissingValueException(false, str);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.thebrokenrail.modupdater.api.impl;
|
||||
|
||||
import com.thebrokenrail.modupdater.api.ConfigObject;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ConfigObjectHardcoded implements ConfigObject {
|
||||
private final Map<String, Object> map;
|
||||
|
||||
public ConfigObjectHardcoded(Map<String, Object> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String str) throws MissingValueException {
|
||||
if (map.containsKey(str)) {
|
||||
try {
|
||||
return (String) map.get(str);
|
||||
} catch (ClassCastException e) {
|
||||
throw new MissingValueException(true, str);
|
||||
}
|
||||
} else {
|
||||
throw new MissingValueException(false, str);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String str) throws MissingValueException {
|
||||
if (map.containsKey(str)) {
|
||||
try {
|
||||
return (Integer) map.get(str);
|
||||
} catch (ClassCastException e) {
|
||||
throw new MissingValueException(true, str);
|
||||
}
|
||||
} else {
|
||||
throw new MissingValueException(false, str);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean(String str) throws MissingValueException {
|
||||
if (map.containsKey(str)) {
|
||||
try {
|
||||
return (Boolean) map.get(str);
|
||||
} catch (ClassCastException e) {
|
||||
throw new MissingValueException(true, str);
|
||||
}
|
||||
} else {
|
||||
throw new MissingValueException(false, str);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package com.thebrokenrail.modupdater.client.gui;
|
||||
|
||||
import com.thebrokenrail.modupdater.ModUpdater;
|
||||
import com.thebrokenrail.modupdater.util.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.data.ModUpdate;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
|
@ -14,12 +14,17 @@ import net.minecraft.client.util.math.MatrixStack;
|
|||
import net.minecraft.text.TranslatableText;
|
||||
import net.minecraft.util.Util;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class ModUpdateScreen extends Screen {
|
||||
private ModUpdateListWidget list;
|
||||
public ModUpdateListWidget list;
|
||||
private ButtonWidget download;
|
||||
private ButtonWidget refresh;
|
||||
private final Screen parent;
|
||||
|
||||
private static final int BOTTOM_ROW = 60;
|
||||
|
||||
public ModUpdateScreen(Screen parent) {
|
||||
super(new TranslatableText("gui." + ModUpdater.NAMESPACE + ".title"));
|
||||
this.parent = parent;
|
||||
|
@ -29,17 +34,21 @@ public class ModUpdateScreen extends Screen {
|
|||
protected void init() {
|
||||
list = new ModUpdateListWidget(client, this);
|
||||
children.add(list);
|
||||
int buttonHeight = 20;
|
||||
int padding = 2;
|
||||
int actionRowY = height - BOTTOM_ROW / 2 - padding - buttonHeight;
|
||||
int doneY = height - BOTTOM_ROW / 2 + padding;
|
||||
int buttonWidth = 150;
|
||||
int paddingX = 5;
|
||||
int doneX = width / 2 - buttonWidth - paddingX;
|
||||
int downloadX = width / 2 + paddingX;
|
||||
download = addButton(new ButtonWidget(downloadX, height - 30, buttonWidth, 20, new TranslatableText("gui." + ModUpdater.NAMESPACE + ".download"), buttonWidget -> {
|
||||
int refreshX = width / 2 - buttonWidth - padding;
|
||||
int downloadX = width / 2 + padding;
|
||||
int doneX = width / 2 - buttonWidth / 2;
|
||||
refresh = addButton(new ButtonWidget(refreshX, actionRowY, buttonWidth, buttonHeight, new TranslatableText("gui." + ModUpdater.NAMESPACE + ".refresh"), buttonWidget -> ModUpdater.findUpdates()));
|
||||
download = addButton(new ButtonWidget(downloadX, actionRowY, buttonWidth, buttonHeight, new TranslatableText("gui." + ModUpdater.NAMESPACE + ".download"), buttonWidget -> {
|
||||
if (list.getSelected() != null) {
|
||||
Util.getOperatingSystem().open(list.getSelected().update.downloadURL);
|
||||
}
|
||||
}));
|
||||
download.active = false;
|
||||
addButton(new ButtonWidget(doneX, height - 30, buttonWidth, 20, ScreenTexts.DONE, buttonWidget -> {
|
||||
addButton(new ButtonWidget(doneX, doneY, buttonWidth, buttonHeight, ScreenTexts.DONE, buttonWidget -> {
|
||||
assert client != null;
|
||||
client.openScreen(parent);
|
||||
}));
|
||||
|
@ -48,6 +57,8 @@ public class ModUpdateScreen extends Screen {
|
|||
|
||||
@Override
|
||||
public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
|
||||
refresh.active = ModUpdater.getUpdates() != null;
|
||||
download.active = list.getSelected() != null;
|
||||
list.render(matrices, mouseX, mouseY, delta);
|
||||
drawCenteredText(matrices, textRenderer, title, width / 2, 16, 16777215);
|
||||
super.render(matrices, mouseX, mouseY, delta);
|
||||
|
@ -56,13 +67,26 @@ public class ModUpdateScreen extends Screen {
|
|||
@Environment(EnvType.CLIENT)
|
||||
private static class ModUpdateListWidget extends EntryListWidget<ModUpdateEntry> {
|
||||
private final ModUpdateScreen screen;
|
||||
private ModUpdate[] updates = null;
|
||||
|
||||
private ModUpdateListWidget(MinecraftClient client, ModUpdateScreen screen) {
|
||||
super(client, screen.width, screen.height, 32, screen.height - 40, 18);
|
||||
super(client, screen.width, screen.height, 32, screen.height - BOTTOM_ROW, 18);
|
||||
this.screen = screen;
|
||||
|
||||
for (ModUpdate update : ModUpdater.getUpdates()) {
|
||||
addEntry(new ModUpdateEntry(update, screen, this));
|
||||
reload();
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
ModUpdate[] newUpdates = ModUpdater.getUpdates();
|
||||
if (!Arrays.equals(updates, newUpdates)) {
|
||||
clearEntries();
|
||||
setSelected(null);
|
||||
if (newUpdates != null) {
|
||||
for (ModUpdate update : newUpdates) {
|
||||
addEntry(new ModUpdateEntry(update, screen, this));
|
||||
}
|
||||
}
|
||||
updates = newUpdates;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,9 +109,6 @@ public class ModUpdateScreen extends Screen {
|
|||
super.setSelected(entry);
|
||||
if (entry != null) {
|
||||
NarratorManager.INSTANCE.narrate(new TranslatableText("narrator.select", entry.update.text).asString());
|
||||
screen.download.active = true;
|
||||
} else {
|
||||
screen.download.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +121,15 @@ public class ModUpdateScreen extends Screen {
|
|||
protected boolean isFocused() {
|
||||
return screen.getFocused() == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
|
||||
reload();
|
||||
super.render(matrices, mouseX, mouseY, delta);
|
||||
if (updates == null) {
|
||||
drawCenteredText(matrices, screen.textRenderer, new TranslatableText("gui.modupdater.loading"), width / 2, (bottom - top) / 2 - screen.textRenderer.fontHeight + top, 16777215);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package com.thebrokenrail.modupdater.command;
|
||||
|
||||
import com.thebrokenrail.modupdater.ModUpdater;
|
||||
import com.thebrokenrail.modupdater.data.ModUpdate;
|
||||
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
|
||||
import net.minecraft.command.CommandException;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.text.ClickEvent;
|
||||
import net.minecraft.text.HoverEvent;
|
||||
import net.minecraft.text.LiteralText;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import net.minecraft.util.Formatting;
|
||||
|
||||
public class ModUpdaterCommand {
|
||||
private static void checkLoaded() throws CommandException {
|
||||
if (ModUpdater.getUpdates() == null) {
|
||||
throw new CommandException(new TranslatableText("commands." + ModUpdater.NAMESPACE + ".not_loaded"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, isDedicated) -> dispatcher.register(CommandManager.literal(ModUpdater.NAMESPACE)
|
||||
.then(CommandManager.literal("list").executes(context -> {
|
||||
checkLoaded();
|
||||
context.getSource().sendFeedback(new TranslatableText("commands." + ModUpdater.NAMESPACE + ".list_title").formatted(Formatting.YELLOW), false);
|
||||
ModUpdate[] updates = ModUpdater.getUpdates();
|
||||
assert updates != null;
|
||||
for (ModUpdate update : updates) {
|
||||
context.getSource().sendFeedback(new LiteralText(update.text).styled(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, update.downloadURL)).withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TranslatableText("commands." + ModUpdater.NAMESPACE + ".hover")))), false);
|
||||
}
|
||||
return updates.length;
|
||||
}))
|
||||
.then(CommandManager.literal("refresh").requires(source -> source.hasPermissionLevel(3)).executes(context -> {
|
||||
checkLoaded();
|
||||
ModUpdater.findUpdates();
|
||||
context.getSource().sendFeedback(new TranslatableText("commands." + ModUpdater.NAMESPACE + ".refresh_start"), true);
|
||||
return 1;
|
||||
}))
|
||||
));
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.thebrokenrail.modupdater.util;
|
||||
package com.thebrokenrail.modupdater.data;
|
||||
|
||||
import net.fabricmc.loader.api.Version;
|
||||
import net.fabricmc.loader.util.version.VersionParsingException;
|
|
@ -2,19 +2,21 @@ package com.thebrokenrail.modupdater.strategy;
|
|||
|
||||
import com.mojang.bridge.game.GameVersion;
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.JsonDataException;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import com.thebrokenrail.modupdater.ModUpdater;
|
||||
import com.thebrokenrail.modupdater.util.ConfigObject;
|
||||
import com.thebrokenrail.modupdater.util.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.util.ModUpdateStrategy;
|
||||
import com.thebrokenrail.modupdater.api.ConfigObject;
|
||||
import com.thebrokenrail.modupdater.api.UpdateStrategy;
|
||||
import com.thebrokenrail.modupdater.data.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.util.Util;
|
||||
import net.fabricmc.loader.api.SemanticVersion;
|
||||
import net.fabricmc.loader.util.version.VersionParsingException;
|
||||
import net.fabricmc.loader.api.VersionParsingException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
class CurseForgeStrategy implements ModUpdateStrategy {
|
||||
public class CurseForgeStrategy implements UpdateStrategy {
|
||||
@SuppressWarnings({"unused", "MismatchedReadAndWriteOfArray"})
|
||||
private static class CurseForgeFile {
|
||||
private String fileName;
|
||||
|
@ -22,26 +24,37 @@ class CurseForgeStrategy implements ModUpdateStrategy {
|
|||
private String[] gameVersion;
|
||||
}
|
||||
|
||||
private final JsonAdapter<CurseForgeFile[]> jsonAdapter;
|
||||
|
||||
public CurseForgeStrategy() {
|
||||
Moshi moshi = new Moshi.Builder().build();
|
||||
jsonAdapter = moshi.adapter(CurseForgeFile[].class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModUpdate checkForUpdate(ConfigObject obj, String oldVersion, String name) {
|
||||
@Nullable
|
||||
public ModUpdate run(ConfigObject obj, String oldVersion, String name, String id) {
|
||||
int projectID;
|
||||
try {
|
||||
projectID = obj.getInt("projectID");
|
||||
} catch (ConfigObject.MissingValueException e) {
|
||||
ModUpdater.invalidModUpdaterConfig(name);
|
||||
ModUpdater.logWarn(name, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
String data = Util.urlToString("https://addons-ecs.forgesvc.net/api/v2/addon/" + projectID + "/files");
|
||||
|
||||
Moshi moshi = new Moshi.Builder().build();
|
||||
JsonAdapter<CurseForgeFile[]> jsonAdapter = moshi.adapter(CurseForgeFile[].class);
|
||||
String data;
|
||||
try {
|
||||
data = Util.urlToString("https://addons-ecs.forgesvc.net/api/v2/addon/" + projectID + "/files");
|
||||
} catch (IOException e) {
|
||||
ModUpdater.logWarn(name, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
CurseForgeFile[] files;
|
||||
try {
|
||||
files = jsonAdapter.fromJson(data);
|
||||
} catch (IOException e) {
|
||||
ModUpdater.getLogger().warn("Unable To Access CurseForge: " + name);
|
||||
} catch (JsonDataException | IOException e) {
|
||||
ModUpdater.logWarn(name, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -52,25 +65,29 @@ class CurseForgeStrategy implements ModUpdateStrategy {
|
|||
String versionStr;
|
||||
GameVersion version = Util.getMinecraftVersion();
|
||||
if (version.isStable()) {
|
||||
versionStr = version.getName();
|
||||
versionStr = version.getId();
|
||||
} else {
|
||||
versionStr = version.getReleaseTarget() + "-Snapshot";
|
||||
}
|
||||
|
||||
boolean strict = isStrict(obj);
|
||||
|
||||
CurseForgeFile newestFile = null;
|
||||
for (CurseForgeFile file : files) {
|
||||
String fileVersion = Util.getVersionFromFileName(file.fileName);
|
||||
if (Arrays.asList(file.gameVersion).contains(versionStr) || Util.isVersionCompatible(fileVersion)) {
|
||||
if (newestFile != null) {
|
||||
String newestFileVersion = Util.getVersionFromFileName(newestFile.fileName);
|
||||
try {
|
||||
if (SemanticVersion.parse(fileVersion).compareTo(SemanticVersion.parse(newestFileVersion)) > 0) {
|
||||
newestFile = file;
|
||||
if (Util.isFileCompatible(file.fileName)) {
|
||||
String fileVersion = Util.getVersionFromFileName(file.fileName);
|
||||
if ((Arrays.asList(file.gameVersion).contains(versionStr) && !strict) || Util.isVersionCompatible(id, fileVersion, strict)) {
|
||||
if (newestFile != null) {
|
||||
String newestFileVersion = Util.getVersionFromFileName(newestFile.fileName);
|
||||
try {
|
||||
if (SemanticVersion.parse(fileVersion).compareTo(SemanticVersion.parse(newestFileVersion)) > 0) {
|
||||
newestFile = file;
|
||||
}
|
||||
} catch (VersionParsingException ignored) {
|
||||
}
|
||||
} catch (VersionParsingException ignored) {
|
||||
} else {
|
||||
newestFile = file;
|
||||
}
|
||||
} else {
|
||||
newestFile = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
package com.thebrokenrail.modupdater.strategy;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.JsonDataException;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import com.thebrokenrail.modupdater.ModUpdater;
|
||||
import com.thebrokenrail.modupdater.api.ConfigObject;
|
||||
import com.thebrokenrail.modupdater.api.UpdateStrategy;
|
||||
import com.thebrokenrail.modupdater.data.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.util.Util;
|
||||
import net.fabricmc.loader.api.SemanticVersion;
|
||||
import net.fabricmc.loader.api.VersionParsingException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
|
||||
public class GitHubReleasesStrategy implements UpdateStrategy {
|
||||
@SuppressWarnings({"unused", "MismatchedReadAndWriteOfArray"})
|
||||
private static class GitHubRelease {
|
||||
private GitHubReleaseAsset[] assets;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class GitHubReleaseAsset {
|
||||
private String name;
|
||||
private String browser_download_url;
|
||||
}
|
||||
|
||||
private final JsonAdapter<GitHubRelease[]> jsonAdapter;
|
||||
|
||||
public GitHubReleasesStrategy() {
|
||||
Moshi moshi = new Moshi.Builder().build();
|
||||
jsonAdapter = moshi.adapter(GitHubRelease[].class).nonNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ModUpdate run(ConfigObject obj, String oldVersion, String name, String id) {
|
||||
String owner;
|
||||
String repo;
|
||||
try {
|
||||
owner = obj.getString("owner");
|
||||
repo = obj.getString("repository");
|
||||
} catch (ConfigObject.MissingValueException e) {
|
||||
ModUpdater.logWarn(name, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
String data;
|
||||
try {
|
||||
data = Util.urlToString(String.format("https://api.github.com/repos/%s/%s/releases", owner, repo));
|
||||
} catch (IOException e) {
|
||||
ModUpdater.logWarn(name, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
GitHubRelease[] releases;
|
||||
try {
|
||||
releases = jsonAdapter.fromJson(data);
|
||||
} catch (JsonDataException | IOException e) {
|
||||
ModUpdater.logWarn(name, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (releases == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean strict = isStrict(obj);
|
||||
|
||||
GitHubReleaseAsset newestFile = null;
|
||||
for (GitHubRelease release : releases) {
|
||||
for (GitHubReleaseAsset asset : release.assets) {
|
||||
if (Util.isFileCompatible(asset.name)) {
|
||||
String fileVersion = Util.getVersionFromFileName(asset.name);
|
||||
if (Util.isVersionCompatible(id, fileVersion, strict)) {
|
||||
if (newestFile != null) {
|
||||
try {
|
||||
if (SemanticVersion.parse(fileVersion).compareTo(SemanticVersion.parse(fileVersion)) > 0) {
|
||||
newestFile = asset;
|
||||
}
|
||||
} catch (VersionParsingException ignored) {
|
||||
}
|
||||
} else {
|
||||
newestFile = asset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newestFile != null) {
|
||||
String newestFileVersion = Util.getVersionFromFileName(newestFile.name);
|
||||
try {
|
||||
if (SemanticVersion.parse(newestFileVersion).compareTo(SemanticVersion.parse(oldVersion)) > 0) {
|
||||
return new ModUpdate(oldVersion, newestFileVersion, newestFile.browser_download_url, name);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (VersionParsingException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package com.thebrokenrail.modupdater.strategy;
|
||||
|
||||
import com.squareup.moshi.JsonAdapter;
|
||||
import com.squareup.moshi.JsonDataException;
|
||||
import com.squareup.moshi.Moshi;
|
||||
import com.squareup.moshi.Types;
|
||||
import com.thebrokenrail.modupdater.ModUpdater;
|
||||
import com.thebrokenrail.modupdater.api.ConfigObject;
|
||||
import com.thebrokenrail.modupdater.api.UpdateStrategy;
|
||||
import com.thebrokenrail.modupdater.data.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.util.Util;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
|
||||
public class JSONStrategy implements UpdateStrategy {
|
||||
@SuppressWarnings("unused")
|
||||
private static class LatestVersionEntry {
|
||||
private String version;
|
||||
private String downloadUrl;
|
||||
}
|
||||
|
||||
private final JsonAdapter<Map<String, LatestVersionEntry>> jsonAdapter;
|
||||
|
||||
public JSONStrategy() {
|
||||
Moshi moshi = new Moshi.Builder().build();
|
||||
Type map = Types.newParameterizedType(Map.class, String.class, LatestVersionEntry.class);
|
||||
jsonAdapter = moshi.adapter(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ModUpdate run(ConfigObject obj, String oldVersion, String name, String id) {
|
||||
String url;
|
||||
try {
|
||||
url = obj.getString("url");
|
||||
} catch (ConfigObject.MissingValueException e) {
|
||||
ModUpdater.logWarn(name, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
String data;
|
||||
try {
|
||||
data = Util.urlToString(url);
|
||||
} catch (IOException e) {
|
||||
ModUpdater.logWarn(name, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, LatestVersionEntry> map;
|
||||
try {
|
||||
map = jsonAdapter.fromJson(data);
|
||||
} catch (JsonDataException | IOException e) {
|
||||
ModUpdater.logWarn(name, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (map == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String version = Util.getMinecraftVersion().getId();
|
||||
if (map.containsKey(version)) {
|
||||
LatestVersionEntry entry = map.get(version);
|
||||
if (!oldVersion.equals(entry.version)) {
|
||||
return new ModUpdate(oldVersion, entry.version, entry.downloadUrl, name);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,46 @@
|
|||
package com.thebrokenrail.modupdater.strategy;
|
||||
|
||||
import com.thebrokenrail.modupdater.ModUpdater;
|
||||
import com.thebrokenrail.modupdater.util.ConfigObject;
|
||||
import com.thebrokenrail.modupdater.util.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.util.ModUpdateStrategy;
|
||||
import com.thebrokenrail.modupdater.api.ConfigObject;
|
||||
import com.thebrokenrail.modupdater.api.UpdateStrategy;
|
||||
import com.thebrokenrail.modupdater.data.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.util.Util;
|
||||
import net.fabricmc.loader.api.SemanticVersion;
|
||||
import net.fabricmc.loader.util.version.VersionParsingException;
|
||||
import org.dom4j.Document;
|
||||
import org.dom4j.DocumentException;
|
||||
import org.dom4j.Node;
|
||||
import org.dom4j.io.SAXReader;
|
||||
import net.fabricmc.loader.api.VersionParsingException;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class MavenStrategy implements UpdateStrategy {
|
||||
private final DocumentBuilder builder;
|
||||
|
||||
public MavenStrategy() {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setValidating(true);
|
||||
factory.setIgnoringElementContentWhitespace(true);
|
||||
try {
|
||||
builder = factory.newDocumentBuilder();
|
||||
} catch (ParserConfigurationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public class MavenStrategy implements ModUpdateStrategy {
|
||||
@Override
|
||||
public ModUpdate checkForUpdate(ConfigObject obj, String oldVersion, String name) {
|
||||
@Nullable
|
||||
public ModUpdate run(ConfigObject obj, String oldVersion, String name, String id) {
|
||||
String repository;
|
||||
String group;
|
||||
String artifact;
|
||||
|
@ -27,27 +49,45 @@ public class MavenStrategy implements ModUpdateStrategy {
|
|||
group = obj.getString("group");
|
||||
artifact = obj.getString("artifact");
|
||||
} catch (ConfigObject.MissingValueException e) {
|
||||
ModUpdater.invalidModUpdaterConfig(name);
|
||||
ModUpdater.logWarn(name, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
String mavenRoot = repository + '/' + group.replaceAll("\\.", "/") + '/' + artifact;
|
||||
String mavenRoot = String.format("%s/%s/%s", repository, group.replaceAll("\\.", "/"), artifact);
|
||||
|
||||
String data;
|
||||
try {
|
||||
data = Util.urlToString(mavenRoot + "/maven-metadata.xml");
|
||||
} catch (IOException e) {
|
||||
ModUpdater.logWarn(name, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
Document doc;
|
||||
try {
|
||||
SAXReader reader = new SAXReader();
|
||||
doc = reader.read(new URL(mavenRoot + "/maven-metadata.xml"));
|
||||
} catch (MalformedURLException | DocumentException e) {
|
||||
ModUpdater.getLogger().warn("Unable To Access Maven Repository: " + name);
|
||||
try (InputStream source = new ByteArrayInputStream(data.getBytes())) {
|
||||
doc = builder.parse(source);
|
||||
} catch (IOException | SAXException e) {
|
||||
ModUpdater.logWarn(name, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Node> versions = doc.selectNodes("/metadata/versioning/versions/*");
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList versions;
|
||||
try {
|
||||
versions = (NodeList) xPath.compile("/metadata/versioning/versions/*").evaluate(doc, XPathConstants.NODESET);
|
||||
} catch (XPathExpressionException e) {
|
||||
ModUpdater.logWarn(name, e.toString());
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean strict = isStrict(obj);
|
||||
|
||||
String newestVersion = null;
|
||||
for (Node node : versions) {
|
||||
String version = node.getText();
|
||||
if (Util.isVersionCompatible(version)) {
|
||||
for (int i = 0; i < versions.getLength(); i++) {
|
||||
Node node = versions.item(i);
|
||||
|
||||
String version = node.getTextContent();
|
||||
if (Util.isVersionCompatible(id, version, strict)) {
|
||||
if (newestVersion != null) {
|
||||
try {
|
||||
if (SemanticVersion.parse(version).compareTo(SemanticVersion.parse(newestVersion)) > 0) {
|
||||
|
@ -64,7 +104,7 @@ public class MavenStrategy implements ModUpdateStrategy {
|
|||
if (newestVersion != null) {
|
||||
try {
|
||||
if (SemanticVersion.parse(newestVersion).compareTo(SemanticVersion.parse(oldVersion)) > 0) {
|
||||
return new ModUpdate(oldVersion, newestVersion, mavenRoot + '/' + newestVersion + '/' + artifact + '-' + newestVersion + Util.JAR_EXTENSION, name);
|
||||
return new ModUpdate(oldVersion, newestVersion, String.format("%s/%s/%s-%s%s", mavenRoot, newestVersion, artifact, newestVersion, Util.JAR_EXTENSION), name);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
package com.thebrokenrail.modupdater.strategy;
|
||||
|
||||
import com.thebrokenrail.modupdater.ModUpdater;
|
||||
import com.thebrokenrail.modupdater.util.ConfigObject;
|
||||
import com.thebrokenrail.modupdater.util.HardcodedData;
|
||||
import com.thebrokenrail.modupdater.util.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.util.ModUpdateStrategy;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.fabricmc.loader.api.metadata.ModMetadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ModUpdateStrategies {
|
||||
private static final Map<String, ModUpdateStrategy> data = new HashMap<>();
|
||||
|
||||
private static ModUpdate checkForUpdate(ModMetadata metadata, ConfigObject obj, String name) {
|
||||
String oldVersion = metadata.getVersion().toString();
|
||||
|
||||
String strategy;
|
||||
try {
|
||||
strategy = obj.getString("strategy");
|
||||
} catch (ConfigObject.MissingValueException e) {
|
||||
ModUpdater.invalidModUpdaterConfig(name);
|
||||
return null;
|
||||
}
|
||||
|
||||
ModUpdateStrategy strategyObj = data.get(strategy);
|
||||
if (strategyObj == null) {
|
||||
ModUpdater.invalidModUpdaterConfig(name);
|
||||
return null;
|
||||
}
|
||||
|
||||
return strategyObj.checkForUpdate(obj, oldVersion, name);
|
||||
}
|
||||
|
||||
public static ModUpdate[] findAvailableUpdates() {
|
||||
List<ModUpdate> updates = new ArrayList<>();
|
||||
|
||||
for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
|
||||
ModMetadata metadata = mod.getMetadata();
|
||||
String name = metadata.getName() + " (" + metadata.getId() + ')';
|
||||
|
||||
ModUpdate update = null;
|
||||
if (metadata.containsCustomValue(ModUpdater.NAMESPACE)) {
|
||||
try {
|
||||
update = checkForUpdate(metadata, new ConfigObject.ConfigObjectCustom(metadata.getCustomValue(ModUpdater.NAMESPACE).getAsObject()), name);
|
||||
} catch (ClassCastException e) {
|
||||
ModUpdater.invalidModUpdaterConfig(name);
|
||||
}
|
||||
} else {
|
||||
ConfigObject obj = HardcodedData.getData(metadata.getId());
|
||||
if (obj != null) {
|
||||
update = checkForUpdate(metadata, obj, name);
|
||||
}
|
||||
}
|
||||
|
||||
if (update != null) {
|
||||
updates.add(update);
|
||||
}
|
||||
}
|
||||
|
||||
return updates.toArray(new ModUpdate[0]);
|
||||
}
|
||||
|
||||
static {
|
||||
data.put("curseforge", new CurseForgeStrategy());
|
||||
data.put("maven", new MavenStrategy());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.thebrokenrail.modupdater.strategy.util;
|
||||
|
||||
import com.thebrokenrail.modupdater.api.UpdateStrategy;
|
||||
import com.thebrokenrail.modupdater.strategy.CurseForgeStrategy;
|
||||
import com.thebrokenrail.modupdater.strategy.GitHubReleasesStrategy;
|
||||
import com.thebrokenrail.modupdater.strategy.JSONStrategy;
|
||||
import com.thebrokenrail.modupdater.strategy.MavenStrategy;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class UpdateStrategyRegistry {
|
||||
private static final Map<String, UpdateStrategy> data = new HashMap<>();
|
||||
|
||||
@Nullable
|
||||
static UpdateStrategy get(String name) {
|
||||
return data.get(name);
|
||||
}
|
||||
|
||||
static {
|
||||
data.put("curseforge", new CurseForgeStrategy());
|
||||
data.put("maven", new MavenStrategy());
|
||||
data.put("github", new GitHubReleasesStrategy());
|
||||
data.put("json", new JSONStrategy());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package com.thebrokenrail.modupdater.strategy.util;
|
||||
|
||||
import com.thebrokenrail.modupdater.ModUpdater;
|
||||
import com.thebrokenrail.modupdater.api.ConfigObject;
|
||||
import com.thebrokenrail.modupdater.api.UpdateStrategy;
|
||||
import com.thebrokenrail.modupdater.api.impl.ConfigObjectCustom;
|
||||
import com.thebrokenrail.modupdater.data.ModUpdate;
|
||||
import com.thebrokenrail.modupdater.util.Util;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.fabricmc.loader.api.metadata.ModMetadata;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class UpdateStrategyRunner {
|
||||
@Nullable
|
||||
private static ModUpdate checkModForUpdate(ModMetadata metadata, Consumer<String> scan) {
|
||||
String name = metadata.getName() + " (" + metadata.getId() + ')';
|
||||
|
||||
ConfigObject obj;
|
||||
if (metadata.containsCustomValue(ModUpdater.NAMESPACE)) {
|
||||
try {
|
||||
obj = new ConfigObjectCustom(metadata.getCustomValue(ModUpdater.NAMESPACE).getAsObject());
|
||||
} catch (ClassCastException e) {
|
||||
ModUpdater.logWarn(name, String.format("\"%s\" Is Not An Object", ModUpdater.NAMESPACE));
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
obj = Util.getHardcodedConfig(metadata.getId());
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String oldVersion = metadata.getVersion().toString();
|
||||
|
||||
String strategy;
|
||||
try {
|
||||
strategy = obj.getString("strategy");
|
||||
} catch (ConfigObject.MissingValueException e) {
|
||||
ModUpdater.logWarn(name, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
UpdateStrategy strategyObj = UpdateStrategyRegistry.get(strategy);
|
||||
if (strategyObj == null) {
|
||||
ModUpdater.logWarn(name, "Invalid Strategy: " + name);
|
||||
return null;
|
||||
}
|
||||
|
||||
scan.accept(name);
|
||||
|
||||
return strategyObj.run(obj, oldVersion, name, metadata.getId());
|
||||
}
|
||||
|
||||
public static ModUpdate[] checkAllModsForUpdates() {
|
||||
ModUpdater.logInfo("Checking For Mod Updates...");
|
||||
|
||||
List<ModUpdate> updates = new ArrayList<>();
|
||||
List<String> scannedMods = new ArrayList<>();
|
||||
|
||||
AtomicInteger remaining = new AtomicInteger(0);
|
||||
|
||||
for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
ModMetadata metadata = mod.getMetadata();
|
||||
|
||||
ModUpdate update = checkModForUpdate(metadata, name -> {
|
||||
synchronized (scannedMods) {
|
||||
scannedMods.add(name);
|
||||
}
|
||||
});
|
||||
|
||||
if (update != null) {
|
||||
ModUpdater.logInfo(update.text + " (" + update.downloadURL + ')');
|
||||
synchronized (updates) {
|
||||
updates.add(update);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
synchronized (remaining) {
|
||||
remaining.decrementAndGet();
|
||||
remaining.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
synchronized (remaining) {
|
||||
remaining.incrementAndGet();
|
||||
}
|
||||
thread.start();
|
||||
}
|
||||
|
||||
synchronized (remaining) {
|
||||
while (remaining.get() > 0) {
|
||||
try {
|
||||
remaining.wait();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModUpdater.logInfo(updates.size() + String.format(" Mod Update%s Found", updates.size() == 1 ? "" : "s"));
|
||||
|
||||
ModUpdater.logInfo("Scanned " + scannedMods.size() + " Mods: " + String.join(", ", scannedMods));
|
||||
|
||||
return updates.toArray(new ModUpdate[0]);
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package com.thebrokenrail.modupdater.util;
|
||||
|
||||
import net.fabricmc.loader.api.metadata.CustomValue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface ConfigObject {
|
||||
String getString(String str) throws MissingValueException;
|
||||
int getInt(String str) throws MissingValueException;
|
||||
|
||||
class ConfigObjectCustom implements ConfigObject {
|
||||
private final CustomValue.CvObject obj;
|
||||
|
||||
public ConfigObjectCustom(CustomValue.CvObject obj) {
|
||||
this.obj = obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String str) throws MissingValueException {
|
||||
if (obj.containsKey(str)) {
|
||||
try {
|
||||
return obj.get(str).getAsString();
|
||||
} catch (ClassCastException e) {
|
||||
throw new MissingValueException();
|
||||
}
|
||||
} else {
|
||||
throw new MissingValueException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String str) throws MissingValueException {
|
||||
if (obj.containsKey(str)) {
|
||||
try {
|
||||
return obj.get(str).getAsNumber().intValue();
|
||||
} catch (ClassCastException e) {
|
||||
throw new MissingValueException();
|
||||
}
|
||||
} else {
|
||||
throw new MissingValueException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigObjectHardcoded implements ConfigObject {
|
||||
private final Map<String, Object> map;
|
||||
|
||||
public ConfigObjectHardcoded(Map<String, Object> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(String str) throws MissingValueException {
|
||||
if (map.containsKey(str)) {
|
||||
try {
|
||||
return (String) map.get(str);
|
||||
} catch (ClassCastException e) {
|
||||
throw new MissingValueException();
|
||||
}
|
||||
} else {
|
||||
throw new MissingValueException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt(String str) throws MissingValueException {
|
||||
if (map.containsKey(str)) {
|
||||
try {
|
||||
return (Integer) map.get(str);
|
||||
} catch (ClassCastException e) {
|
||||
throw new MissingValueException();
|
||||
}
|
||||
} else {
|
||||
throw new MissingValueException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MissingValueException extends Exception {
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package com.thebrokenrail.modupdater.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class HardcodedData {
|
||||
public static ConfigObject getData(String modID) {
|
||||
switch (modID) {
|
||||
case "fabric": {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("strategy", "maven");
|
||||
map.put("repository", "https://maven.fabricmc.net");
|
||||
map.put("group", "net.fabricmc.fabric-api");
|
||||
map.put("artifact", "fabric-api");
|
||||
return new ConfigObject.ConfigObjectHardcoded(map);
|
||||
}
|
||||
case "modmenu": {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("strategy", "curseforge");
|
||||
map.put("projectID", 308702);
|
||||
return new ConfigObject.ConfigObjectHardcoded(map);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
package com.thebrokenrail.modupdater.util;
|
||||
|
||||
public interface ModUpdateStrategy {
|
||||
ModUpdate checkForUpdate(ConfigObject obj, String oldVersion, String name);
|
||||
}
|
|
@ -1,42 +1,47 @@
|
|||
package com.thebrokenrail.modupdater.util;
|
||||
|
||||
import com.mojang.bridge.game.GameVersion;
|
||||
import com.thebrokenrail.modupdater.ModUpdater;
|
||||
import com.thebrokenrail.modupdater.api.ConfigObject;
|
||||
import com.thebrokenrail.modupdater.api.entrypoint.ModUpdaterEntryPoint;
|
||||
import com.thebrokenrail.modupdater.api.impl.ConfigObjectHardcoded;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
|
||||
import net.minecraft.MinecraftVersion;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class Util {
|
||||
public static String urlToString(String urlStr) {
|
||||
try {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
URL url = new URL(urlStr);
|
||||
public static String urlToString(String urlStr) throws IOException {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
URL url = new URL(urlStr);
|
||||
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openConnection().getInputStream()))) {
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
stringBuilder.append(line);
|
||||
stringBuilder.append('\n');
|
||||
}
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openConnection().getInputStream()))) {
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
stringBuilder.append(line);
|
||||
stringBuilder.append('\n');
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
public static final String JAR_EXTENSION = ".jar";
|
||||
|
||||
public static String getVersionFromFileName(String fileName) {
|
||||
int index = fileName.indexOf("-");
|
||||
fileName = fileName.substring(index != -1 ? index + 1 : 0);
|
||||
while (!Character.isDigit(fileName.charAt(0))) {
|
||||
int index = fileName.indexOf("-");
|
||||
fileName = fileName.substring(index != -1 ? index + 1 : 0);
|
||||
}
|
||||
if (fileName.endsWith(JAR_EXTENSION)) {
|
||||
fileName = fileName.substring(0, fileName.length() - JAR_EXTENSION.length());
|
||||
}
|
||||
|
@ -64,17 +69,60 @@ public class Util {
|
|||
}
|
||||
}
|
||||
|
||||
private static boolean isVersionCompatible(String versionStr, char prefix) {
|
||||
private static String getMajorVersion() {
|
||||
updateMinecraftVersion();
|
||||
return versionStr.endsWith(prefix + minecraftVersionSemantic) || versionStr.endsWith(prefix + minecraftVersion.getReleaseTarget());
|
||||
String[] parts = minecraftVersion.getReleaseTarget().split("\\.");
|
||||
if (parts.length > 1) {
|
||||
return String.format("%s.%s", parts[0], parts[1]);
|
||||
} else {
|
||||
return minecraftVersion.getId();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isVersionCompatible(String versionStr) {
|
||||
return isVersionCompatible(versionStr,'+') || isVersionCompatible(versionStr, '-');
|
||||
private static boolean isVersionCompatible(String versionStr, char prefix, boolean strict) {
|
||||
updateMinecraftVersion();
|
||||
return versionStr.endsWith(prefix + minecraftVersionSemantic) || versionStr.endsWith(prefix + minecraftVersion.getId()) || (!strict && (versionStr.endsWith(prefix + minecraftVersion.getReleaseTarget()) || versionStr.endsWith(prefix + getMajorVersion())));
|
||||
}
|
||||
|
||||
public static boolean isVersionCompatible(String id, String versionStr, boolean strict) {
|
||||
List<EntrypointContainer<ModUpdaterEntryPoint>> list = FabricLoader.getInstance().getEntrypointContainers(ModUpdater.NAMESPACE, ModUpdaterEntryPoint.class);
|
||||
for (EntrypointContainer<ModUpdaterEntryPoint> container : list) {
|
||||
if (container.getProvider().getMetadata().getId().equals(id)) {
|
||||
return container.getEntrypoint().isVersionCompatible(versionStr);
|
||||
}
|
||||
}
|
||||
|
||||
return isVersionCompatible(versionStr, '+', strict) || isVersionCompatible(versionStr, '-', strict);
|
||||
}
|
||||
|
||||
public static boolean isFileCompatible(String fileName) {
|
||||
return !fileName.endsWith("-dev" + JAR_EXTENSION) && !fileName.endsWith("-sources" + JAR_EXTENSION) && !fileName.endsWith("-sources-dev" + JAR_EXTENSION) && fileName.endsWith(JAR_EXTENSION);
|
||||
}
|
||||
|
||||
public static GameVersion getMinecraftVersion() {
|
||||
updateMinecraftVersion();
|
||||
return minecraftVersion;
|
||||
}
|
||||
|
||||
public static ConfigObject getHardcodedConfig(String modID) {
|
||||
switch (modID) {
|
||||
case "fabric": {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("strategy", "curseforge");
|
||||
map.put("projectID", 306612);
|
||||
map.put("strict", false);
|
||||
return new ConfigObjectHardcoded(map);
|
||||
}
|
||||
case "modmenu": {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("strategy", "curseforge");
|
||||
map.put("projectID", 308702);
|
||||
map.put("strict", false);
|
||||
return new ConfigObjectHardcoded(map);
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"gui.modupdater.title": "Available Mod Updates",
|
||||
"gui.modupdater.download": "Download"
|
||||
"gui.modupdater.download": "Download",
|
||||
"gui.modupdater.refresh": "Refresh",
|
||||
"gui.modupdater.loading": "Loading...",
|
||||
"commands.modupdater.not_loaded": "Unable To Perform Operation While Mod Updates Are Loading",
|
||||
"commands.modupdater.refresh_start": "Refreshing Mod Updates",
|
||||
"commands.modupdater.hover": "Click To Download",
|
||||
"commands.modupdater.list_title": "Available Mod Updates:"
|
||||
}
|
10
src/main/resources/assets/modupdater/lang/zh_cn.json
Normal file
10
src/main/resources/assets/modupdater/lang/zh_cn.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"gui.modupdater.title": "可用的模组更新",
|
||||
"gui.modupdater.download": "下载",
|
||||
"gui.modupdater.refresh": "刷新",
|
||||
"gui.modupdater.loading": "加载中...",
|
||||
"commands.modupdater.not_loaded": "模组更新加载时无法进行其它操作",
|
||||
"commands.modupdater.refresh_start": "刷新模组更新列表中",
|
||||
"commands.modupdater.hover": "点击下载",
|
||||
"commands.modupdater.list_title": "可用的模组更新:"
|
||||
}
|
Reference in New Issue
Block a user