1.1.0
ModUpdater/pipeline/head This commit looks good Details

This commit is contained in:
TheBrokenRail 2020-06-25 16:56:09 -04:00
parent 1a48e714b0
commit 897cec88d3
19 changed files with 293 additions and 250 deletions

View File

@ -1,5 +1,9 @@
# Changelog
**1.1.0**
* Stabilize
* Improve Error Messages
**1.0.11**
* Close Input Stream Properly

View File

@ -9,7 +9,7 @@ org.gradle.jvmargs = -Xmx1G
fabric_loader_version = 0.8.8+build.202
# Mod Properties
mod_version = 1.0.11
mod_version = 1.1.0
maven_group = com.thebrokenrail
# Dependencies

View File

@ -1,7 +1,7 @@
package com.thebrokenrail.modupdater;
import com.thebrokenrail.modupdater.strategy.ModUpdateStrategies;
import com.thebrokenrail.modupdater.util.ModUpdate;
import com.thebrokenrail.modupdater.strategy.util.UpdateStrategyRunner;
import com.thebrokenrail.modupdater.data.ModUpdate;
import net.fabricmc.api.ModInitializer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -13,12 +13,12 @@ public class ModUpdater implements ModInitializer {
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 log(String name, String msg) {
getLogger().warn(String.format("%s: %s", name, msg));
}
private static volatile ModUpdate[] updates;
@ -28,7 +28,7 @@ public class ModUpdater implements ModInitializer {
public static ModUpdate[] getUpdates() {
if (updates == null) {
if (Thread.currentThread() == updateThread) {
updates = ModUpdateStrategies.findAvailableUpdates();
updates = UpdateStrategyRunner.checkAllModsForUpdates();
} else {
return null;
}

View File

@ -0,0 +1,15 @@
package com.thebrokenrail.modupdater.api;
public interface ConfigObject {
String getString(String str) throws MissingValueException;
int getInt(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));
}
}
}

View File

@ -0,0 +1,10 @@
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);
}

View File

@ -0,0 +1,38 @@
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);
}
}
}

View File

@ -0,0 +1,39 @@
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);
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -5,17 +5,18 @@ 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.data.ModUpdate;
import com.thebrokenrail.modupdater.api.UpdateStrategy;
import com.thebrokenrail.modupdater.util.Util;
import net.fabricmc.loader.api.SemanticVersion;
import net.fabricmc.loader.util.version.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;
@ -24,12 +25,13 @@ class CurseForgeStrategy implements ModUpdateStrategy {
}
@Override
public ModUpdate checkForUpdate(ConfigObject obj, String oldVersion, String name) {
@Nullable
public ModUpdate run(ConfigObject obj, String oldVersion, String name) {
int projectID;
try {
projectID = obj.getInt("projectID");
} catch (ConfigObject.MissingValueException e) {
ModUpdater.invalidModUpdaterConfig(name);
ModUpdater.log(name, e.getMessage());
return null;
}
@ -37,7 +39,7 @@ class CurseForgeStrategy implements ModUpdateStrategy {
try {
data = Util.urlToString("https://addons-ecs.forgesvc.net/api/v2/addon/" + projectID + "/files");
} catch (IOException e) {
ModUpdater.getLogger().warn("Unable To Access CurseForge: " + name);
ModUpdater.log(name, e.toString());
return null;
}
@ -48,7 +50,7 @@ class CurseForgeStrategy implements ModUpdateStrategy {
files = jsonAdapter.fromJson(data);
} catch (JsonDataException | IOException e) {
ModUpdater.getLogger().warn("CurseForge Sent Invalid Data: ", e);
ModUpdater.log(name, e.toString());
return null;
}

View File

@ -4,16 +4,17 @@ 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.data.ModUpdate;
import com.thebrokenrail.modupdater.api.UpdateStrategy;
import com.thebrokenrail.modupdater.util.Util;
import net.fabricmc.loader.api.SemanticVersion;
import net.fabricmc.loader.util.version.VersionParsingException;
import javax.annotation.Nullable;
import java.io.IOException;
public class GitHubReleasesStrategy implements ModUpdateStrategy {
public class GitHubReleasesStrategy implements UpdateStrategy {
@SuppressWarnings({"unused", "MismatchedReadAndWriteOfArray"})
private static class GitHubRelease {
private GitHubReleaseAsset[] assets;
@ -26,14 +27,15 @@ public class GitHubReleasesStrategy implements ModUpdateStrategy {
}
@Override
public ModUpdate checkForUpdate(ConfigObject obj, String oldVersion, String name) {
@Nullable
public ModUpdate run(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);
ModUpdater.log(name, e.getMessage());
return null;
}
@ -41,7 +43,7 @@ public class GitHubReleasesStrategy implements ModUpdateStrategy {
try {
data = Util.urlToString(String.format("https://api.github.com/repos/%s/%s/releases", owner, repo));
} catch (IOException e) {
ModUpdater.getLogger().warn("Unable To Access GitHub: " + name);
ModUpdater.log(name, e.toString());
return null;
}
@ -53,7 +55,7 @@ public class GitHubReleasesStrategy implements ModUpdateStrategy {
// GitHub's API never omits values, they're always null
releases = jsonAdapter.nonNull().fromJson(data);
} catch (JsonDataException | IOException e) {
ModUpdater.getLogger().warn("GitHub Sent Invalid Data: ", e);
ModUpdater.log(name, e.toString());
return null;
}

View File

@ -1,9 +1,9 @@
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.data.ModUpdate;
import com.thebrokenrail.modupdater.api.UpdateStrategy;
import com.thebrokenrail.modupdater.util.Util;
import net.fabricmc.loader.api.SemanticVersion;
import net.fabricmc.loader.util.version.VersionParsingException;
@ -12,14 +12,16 @@ import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import javax.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MavenStrategy implements ModUpdateStrategy {
public class MavenStrategy implements UpdateStrategy {
@Override
public ModUpdate checkForUpdate(ConfigObject obj, String oldVersion, String name) {
@Nullable
public ModUpdate run(ConfigObject obj, String oldVersion, String name) {
String repository;
String group;
String artifact;
@ -28,7 +30,7 @@ public class MavenStrategy implements ModUpdateStrategy {
group = obj.getString("group");
artifact = obj.getString("artifact");
} catch (ConfigObject.MissingValueException e) {
ModUpdater.invalidModUpdaterConfig(name);
ModUpdater.log(name, e.getMessage());
return null;
}
@ -38,7 +40,7 @@ public class MavenStrategy implements ModUpdateStrategy {
try {
data = Util.urlToString(mavenRoot + "/maven-metadata.xml");
} catch (IOException e) {
ModUpdater.getLogger().warn("Unable To Access Maven Repository: " + name);
ModUpdater.log(name, e.toString());
return null;
}
@ -46,11 +48,8 @@ public class MavenStrategy implements ModUpdateStrategy {
try (InputStream source = new ByteArrayInputStream(data.getBytes())) {
SAXReader reader = new SAXReader();
doc = reader.read(source);
} catch (DocumentException e) {
ModUpdater.getLogger().warn("Maven Repository Sent Invalid Data: " + name);
return null;
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException | IOException e) {
ModUpdater.log(name, e.toString());
return null;
}

View File

@ -1,99 +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;
import java.util.concurrent.atomic.AtomicInteger;
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<>();
AtomicInteger remaining = new AtomicInteger(0);
for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
Thread thread = new Thread(() -> {
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) {
synchronized (updates) {
updates.add(update);
}
}
synchronized (remaining) {
remaining.decrementAndGet();
remaining.notifyAll();
}
});
synchronized (remaining) {
remaining.incrementAndGet();
}
thread.start();
}
synchronized (remaining) {
while (remaining.get() > 0) {
try {
remaining.wait();
} catch (InterruptedException ignored) {
}
}
}
return updates.toArray(new ModUpdate[0]);
}
static {
data.put("curseforge", new CurseForgeStrategy());
data.put("maven", new MavenStrategy());
data.put("github", new GitHubReleasesStrategy());
}
}

View File

@ -0,0 +1,25 @@
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.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());
}
}

View File

@ -0,0 +1,96 @@
package com.thebrokenrail.modupdater.strategy.util;
import com.thebrokenrail.modupdater.ModUpdater;
import com.thebrokenrail.modupdater.api.ConfigObject;
import com.thebrokenrail.modupdater.api.impl.ConfigObjectCustom;
import com.thebrokenrail.modupdater.data.ModUpdate;
import com.thebrokenrail.modupdater.api.UpdateStrategy;
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;
public class UpdateStrategyRunner {
@Nullable
private static ModUpdate checkModForUpdate(ModMetadata metadata) {
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.log(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.log(name, e.getMessage());
return null;
}
UpdateStrategy strategyObj = UpdateStrategyRegistry.get(strategy);
if (strategyObj == null) {
ModUpdater.log(name, "Invalid Strategy: " + name);
return null;
}
return strategyObj.run(obj, oldVersion, name);
}
public static ModUpdate[] checkAllModsForUpdates() {
List<ModUpdate> updates = new ArrayList<>();
AtomicInteger remaining = new AtomicInteger(0);
for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
Thread thread = new Thread(() -> {
ModMetadata metadata = mod.getMetadata();
ModUpdate update = checkModForUpdate(metadata);
if (update != null) {
synchronized (updates) {
updates.add(update);
}
}
synchronized (remaining) {
remaining.decrementAndGet();
remaining.notifyAll();
}
});
synchronized (remaining) {
remaining.incrementAndGet();
}
thread.start();
}
synchronized (remaining) {
while (remaining.get() > 0) {
try {
remaining.wait();
} catch (InterruptedException ignored) {
}
}
}
return updates.toArray(new ModUpdate[0]);
}
}

View File

@ -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 {
}
}

View File

@ -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;
}
}
}
}

View File

@ -1,5 +0,0 @@
package com.thebrokenrail.modupdater.util;
public interface ModUpdateStrategy {
ModUpdate checkForUpdate(ConfigObject obj, String oldVersion, String name);
}

View File

@ -1,6 +1,8 @@
package com.thebrokenrail.modupdater.util;
import com.mojang.bridge.game.GameVersion;
import com.thebrokenrail.modupdater.api.ConfigObject;
import com.thebrokenrail.modupdater.api.impl.ConfigObjectHardcoded;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.MinecraftVersion;
@ -9,6 +11,8 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class Util {
@ -88,4 +92,26 @@ public class Util {
updateMinecraftVersion();
return minecraftVersion;
}
public static ConfigObject getHardcodedConfig(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 ConfigObjectHardcoded(map);
}
case "modmenu": {
Map<String, Object> map = new HashMap<>();
map.put("strategy", "curseforge");
map.put("projectID", 308702);
return new ConfigObjectHardcoded(map);
}
default: {
return null;
}
}
}
}