Compare commits
2 Commits
17f56fd400
...
d6420cdf5f
Author | SHA1 | Date | |
---|---|---|---|
d6420cdf5f | |||
0a941684f8 |
@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
**1.0.3**
|
||||||
|
* Add Support For GitHub Releases
|
||||||
|
* Use Multi-Threading
|
||||||
|
|
||||||
**1.0.2**
|
**1.0.2**
|
||||||
* Improve Errors
|
* Improve Errors
|
||||||
* Improve GUI
|
* Improve GUI
|
||||||
|
16
README.md
16
README.md
@ -35,6 +35,19 @@ Place this in your ``fabric.mod.json``:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**GitHub Releases**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"custom": {
|
||||||
|
"modupdater": {
|
||||||
|
"strategy": "github",
|
||||||
|
"owner": "Repository Owner",
|
||||||
|
"repository": "Repository Name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Also replace this in ````build.gradle````:
|
Also replace this in ````build.gradle````:
|
||||||
```gradle
|
```gradle
|
||||||
version = project.mod_version
|
version = project.mod_version
|
||||||
@ -48,4 +61,5 @@ version = "${project.mod_version}+${project.minecraft_version}"
|
|||||||
[View Changelog](CHANGELOG.md)
|
[View Changelog](CHANGELOG.md)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
The icon was created by ``ProspectorDev``.
|
- The icon was created by ``ProspectorDev``
|
||||||
|
- The GitHub Releases strategy was written by ``AppleTheGolden``
|
@ -9,7 +9,7 @@ org.gradle.jvmargs = -Xmx1G
|
|||||||
fabric_loader_version = 0.8.8+build.202
|
fabric_loader_version = 0.8.8+build.202
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version = 1.0.2
|
mod_version = 1.0.3
|
||||||
maven_group = com.thebrokenrail
|
maven_group = com.thebrokenrail
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
@ -6,6 +6,8 @@ import net.fabricmc.api.ModInitializer;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class ModUpdater implements ModInitializer {
|
public class ModUpdater implements ModInitializer {
|
||||||
public static final String NAMESPACE = "modupdater";
|
public static final String NAMESPACE = "modupdater";
|
||||||
|
|
||||||
@ -19,21 +21,30 @@ public class ModUpdater implements ModInitializer {
|
|||||||
getLogger().warn("Invalid JSON Configuration: " + modID);
|
getLogger().warn("Invalid JSON Configuration: " + modID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModUpdate[] updates;
|
private static volatile ModUpdate[] updates;
|
||||||
|
|
||||||
|
private static Thread updateThread;
|
||||||
|
|
||||||
public static ModUpdate[] getUpdates() {
|
public static ModUpdate[] getUpdates() {
|
||||||
if (updates == null) {
|
if (updates == null) {
|
||||||
updates = ModUpdateStrategies.findAvailableUpdates();
|
if (Thread.currentThread() == updateThread) {
|
||||||
|
updates = ModUpdateStrategies.findAvailableUpdates();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return updates;
|
return updates;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
getLogger().info("Checking For Mod Updates...");
|
updateThread = new Thread(() -> {
|
||||||
for (ModUpdate update : getUpdates()) {
|
getLogger().info("Checking For Mod Updates...");
|
||||||
getLogger().info(update.text + " (" + update.downloadURL + ')');
|
for (ModUpdate update : Objects.requireNonNull(getUpdates())) {
|
||||||
}
|
getLogger().info(update.text + " (" + update.downloadURL + ')');
|
||||||
getLogger().info(updates.length + " Mod Update(s) Found");
|
}
|
||||||
|
getLogger().info(updates.length + " Mod Update(s) Found");
|
||||||
|
});
|
||||||
|
updateThread.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,11 @@ import net.minecraft.client.util.math.MatrixStack;
|
|||||||
import net.minecraft.text.TranslatableText;
|
import net.minecraft.text.TranslatableText;
|
||||||
import net.minecraft.util.Util;
|
import net.minecraft.util.Util;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
@Environment(EnvType.CLIENT)
|
@Environment(EnvType.CLIENT)
|
||||||
public class ModUpdateScreen extends Screen {
|
public class ModUpdateScreen extends Screen {
|
||||||
private ModUpdateListWidget list;
|
public ModUpdateListWidget list;
|
||||||
private ButtonWidget download;
|
private ButtonWidget download;
|
||||||
private final Screen parent;
|
private final Screen parent;
|
||||||
|
|
||||||
@ -56,13 +58,25 @@ public class ModUpdateScreen extends Screen {
|
|||||||
@Environment(EnvType.CLIENT)
|
@Environment(EnvType.CLIENT)
|
||||||
private static class ModUpdateListWidget extends EntryListWidget<ModUpdateEntry> {
|
private static class ModUpdateListWidget extends EntryListWidget<ModUpdateEntry> {
|
||||||
private final ModUpdateScreen screen;
|
private final ModUpdateScreen screen;
|
||||||
|
private ModUpdate[] updates = null;
|
||||||
|
|
||||||
private ModUpdateListWidget(MinecraftClient client, ModUpdateScreen screen) {
|
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 - 40, 18);
|
||||||
this.screen = screen;
|
this.screen = screen;
|
||||||
|
|
||||||
for (ModUpdate update : ModUpdater.getUpdates()) {
|
reload();
|
||||||
addEntry(new ModUpdateEntry(update, screen, this));
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
ModUpdate[] newUpdates = ModUpdater.getUpdates();
|
||||||
|
if (!Arrays.equals(updates, newUpdates)) {
|
||||||
|
clearEntries();
|
||||||
|
if (newUpdates != null) {
|
||||||
|
for (ModUpdate update : newUpdates) {
|
||||||
|
addEntry(new ModUpdateEntry(update, screen, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updates = newUpdates;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +114,15 @@ public class ModUpdateScreen extends Screen {
|
|||||||
protected boolean isFocused() {
|
protected boolean isFocused() {
|
||||||
return screen.getFocused() == this;
|
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, height / 2 - screen.textRenderer.fontHeight, 16777215);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Environment(EnvType.CLIENT)
|
@Environment(EnvType.CLIENT)
|
||||||
|
@ -2,6 +2,7 @@ package com.thebrokenrail.modupdater.strategy;
|
|||||||
|
|
||||||
import com.mojang.bridge.game.GameVersion;
|
import com.mojang.bridge.game.GameVersion;
|
||||||
import com.squareup.moshi.JsonAdapter;
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.JsonDataException;
|
||||||
import com.squareup.moshi.Moshi;
|
import com.squareup.moshi.Moshi;
|
||||||
import com.thebrokenrail.modupdater.ModUpdater;
|
import com.thebrokenrail.modupdater.ModUpdater;
|
||||||
import com.thebrokenrail.modupdater.util.ConfigObject;
|
import com.thebrokenrail.modupdater.util.ConfigObject;
|
||||||
@ -43,6 +44,9 @@ class CurseForgeStrategy implements ModUpdateStrategy {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
ModUpdater.getLogger().warn("Unable To Access CurseForge: " + name);
|
ModUpdater.getLogger().warn("Unable To Access CurseForge: " + name);
|
||||||
return null;
|
return null;
|
||||||
|
} catch (JsonDataException e) {
|
||||||
|
ModUpdater.getLogger().warn("CurseForge Sent Invalid Data: ", e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
@ -59,18 +63,20 @@ class CurseForgeStrategy implements ModUpdateStrategy {
|
|||||||
|
|
||||||
CurseForgeFile newestFile = null;
|
CurseForgeFile newestFile = null;
|
||||||
for (CurseForgeFile file : files) {
|
for (CurseForgeFile file : files) {
|
||||||
String fileVersion = Util.getVersionFromFileName(file.fileName);
|
if (Util.isFileCompatible(file.fileName)) {
|
||||||
if (Arrays.asList(file.gameVersion).contains(versionStr) || Util.isVersionCompatible(fileVersion)) {
|
String fileVersion = Util.getVersionFromFileName(file.fileName);
|
||||||
if (newestFile != null) {
|
if (Arrays.asList(file.gameVersion).contains(versionStr) || Util.isVersionCompatible(fileVersion)) {
|
||||||
String newestFileVersion = Util.getVersionFromFileName(newestFile.fileName);
|
if (newestFile != null) {
|
||||||
try {
|
String newestFileVersion = Util.getVersionFromFileName(newestFile.fileName);
|
||||||
if (SemanticVersion.parse(fileVersion).compareTo(SemanticVersion.parse(newestFileVersion)) > 0) {
|
try {
|
||||||
newestFile = file;
|
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,97 @@
|
|||||||
|
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.util.ConfigObject;
|
||||||
|
import com.thebrokenrail.modupdater.util.ModUpdate;
|
||||||
|
import com.thebrokenrail.modupdater.util.ModUpdateStrategy;
|
||||||
|
import com.thebrokenrail.modupdater.util.Util;
|
||||||
|
import net.fabricmc.loader.api.SemanticVersion;
|
||||||
|
import net.fabricmc.loader.util.version.VersionParsingException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class GitHubReleasesStrategy implements ModUpdateStrategy {
|
||||||
|
@SuppressWarnings({"unused", "MismatchedReadAndWriteOfArray"})
|
||||||
|
private static class GitHubRelease {
|
||||||
|
private GitHubReleaseAsset[] assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class GitHubReleaseAsset {
|
||||||
|
private String name;
|
||||||
|
private String browser_download_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModUpdate checkForUpdate(ConfigObject obj, String oldVersion, String name) {
|
||||||
|
String owner;
|
||||||
|
String repo;
|
||||||
|
try {
|
||||||
|
owner = obj.getString("owner");
|
||||||
|
repo = obj.getString("repository");
|
||||||
|
} catch (ConfigObject.MissingValueException e) {
|
||||||
|
ModUpdater.invalidModUpdaterConfig(name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String data = Util.urlToString(String.format("https://api.github.com/repos/%s/%s/releases", owner, repo));
|
||||||
|
|
||||||
|
Moshi moshi = new Moshi.Builder().build();
|
||||||
|
JsonAdapter<GitHubRelease[]> jsonAdapter = moshi.adapter(GitHubRelease[].class);
|
||||||
|
|
||||||
|
GitHubRelease[] releases;
|
||||||
|
try {
|
||||||
|
// GitHub's API never omits values, they're always null
|
||||||
|
releases = jsonAdapter.nonNull().fromJson(data);
|
||||||
|
} catch (IOException e) {
|
||||||
|
ModUpdater.getLogger().warn("Unable To Access GitHub: " + name);
|
||||||
|
return null;
|
||||||
|
} catch (JsonDataException e) {
|
||||||
|
ModUpdater.getLogger().warn("GitHub Sent Invalid Data: ", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releases == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(fileVersion)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,7 @@ public class MavenStrategy implements ModUpdateStrategy {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String mavenRoot = repository + '/' + group.replaceAll("\\.", "/") + '/' + artifact;
|
String mavenRoot = String.format("%s/%s/%s", repository, group.replaceAll("\\.", "/"), artifact);
|
||||||
|
|
||||||
Document doc;
|
Document doc;
|
||||||
try {
|
try {
|
||||||
|
@ -13,6 +13,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class ModUpdateStrategies {
|
public class ModUpdateStrategies {
|
||||||
private static final Map<String, ModUpdateStrategy> data = new HashMap<>();
|
private static final Map<String, ModUpdateStrategy> data = new HashMap<>();
|
||||||
@ -40,26 +41,50 @@ public class ModUpdateStrategies {
|
|||||||
public static ModUpdate[] findAvailableUpdates() {
|
public static ModUpdate[] findAvailableUpdates() {
|
||||||
List<ModUpdate> updates = new ArrayList<>();
|
List<ModUpdate> updates = new ArrayList<>();
|
||||||
|
|
||||||
|
AtomicInteger remaining = new AtomicInteger(0);
|
||||||
|
|
||||||
for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
|
for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
|
||||||
ModMetadata metadata = mod.getMetadata();
|
Thread thread = new Thread(() -> {
|
||||||
String name = metadata.getName() + " (" + metadata.getId() + ')';
|
ModMetadata metadata = mod.getMetadata();
|
||||||
|
String name = metadata.getName() + " (" + metadata.getId() + ')';
|
||||||
|
|
||||||
ModUpdate update = null;
|
ModUpdate update = null;
|
||||||
if (metadata.containsCustomValue(ModUpdater.NAMESPACE)) {
|
if (metadata.containsCustomValue(ModUpdater.NAMESPACE)) {
|
||||||
try {
|
try {
|
||||||
update = checkForUpdate(metadata, new ConfigObject.ConfigObjectCustom(metadata.getCustomValue(ModUpdater.NAMESPACE).getAsObject()), name);
|
update = checkForUpdate(metadata, new ConfigObject.ConfigObjectCustom(metadata.getCustomValue(ModUpdater.NAMESPACE).getAsObject()), name);
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
ModUpdater.invalidModUpdaterConfig(name);
|
ModUpdater.invalidModUpdaterConfig(name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ConfigObject obj = HardcodedData.getData(metadata.getId());
|
||||||
|
if (obj != null) {
|
||||||
|
update = checkForUpdate(metadata, obj, name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ConfigObject obj = HardcodedData.getData(metadata.getId());
|
if (update != null) {
|
||||||
if (obj != null) {
|
synchronized (updates) {
|
||||||
update = checkForUpdate(metadata, obj, name);
|
updates.add(update);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized (remaining) {
|
||||||
|
remaining.decrementAndGet();
|
||||||
|
remaining.notifyAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
synchronized (remaining) {
|
||||||
|
remaining.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
if (update != null) {
|
synchronized (remaining) {
|
||||||
updates.add(update);
|
while (remaining.get() > 0) {
|
||||||
|
try {
|
||||||
|
remaining.wait();
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,5 +94,6 @@ public class ModUpdateStrategies {
|
|||||||
static {
|
static {
|
||||||
data.put("curseforge", new CurseForgeStrategy());
|
data.put("curseforge", new CurseForgeStrategy());
|
||||||
data.put("maven", new MavenStrategy());
|
data.put("maven", new MavenStrategy());
|
||||||
|
data.put("github", new GitHubReleasesStrategy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,10 @@ public class Util {
|
|||||||
public static final String JAR_EXTENSION = ".jar";
|
public static final String JAR_EXTENSION = ".jar";
|
||||||
|
|
||||||
public static String getVersionFromFileName(String fileName) {
|
public static String getVersionFromFileName(String fileName) {
|
||||||
int index = fileName.indexOf("-");
|
while (!Character.isDigit(fileName.charAt(0))) {
|
||||||
fileName = fileName.substring(index != -1 ? index + 1 : 0);
|
int index = fileName.indexOf("-");
|
||||||
|
fileName = fileName.substring(index != -1 ? index + 1 : 0);
|
||||||
|
}
|
||||||
if (fileName.endsWith(JAR_EXTENSION)) {
|
if (fileName.endsWith(JAR_EXTENSION)) {
|
||||||
fileName = fileName.substring(0, fileName.length() - JAR_EXTENSION.length());
|
fileName = fileName.substring(0, fileName.length() - JAR_EXTENSION.length());
|
||||||
}
|
}
|
||||||
@ -73,6 +75,10 @@ public class Util {
|
|||||||
return isVersionCompatible(versionStr,'+') || isVersionCompatible(versionStr, '-');
|
return isVersionCompatible(versionStr,'+') || isVersionCompatible(versionStr, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isFileCompatible(String fileName) {
|
||||||
|
return !fileName.endsWith("-dev" + JAR_EXTENSION) && !fileName.endsWith("-sources" + JAR_EXTENSION) && !fileName.endsWith("-sources-dev" + JAR_EXTENSION);
|
||||||
|
}
|
||||||
|
|
||||||
public static GameVersion getMinecraftVersion() {
|
public static GameVersion getMinecraftVersion() {
|
||||||
updateMinecraftVersion();
|
updateMinecraftVersion();
|
||||||
return minecraftVersion;
|
return minecraftVersion;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"gui.modupdater.title": "Available Mod Updates",
|
"gui.modupdater.title": "Available Mod Updates",
|
||||||
"gui.modupdater.download": "Download"
|
"gui.modupdater.download": "Download",
|
||||||
|
"gui.modupdater.loading": "Loading..."
|
||||||
}
|
}
|
Reference in New Issue
Block a user