Compare commits
143 Commits
Author | SHA1 | Date | |
---|---|---|---|
20dc34714e | |||
267bd3644c | |||
d6515d9cbb | |||
5e6eff241e | |||
204198f143 | |||
9fdc4f4991 | |||
b88d7f3e08 | |||
ebbb92f66d | |||
6b9d4fa968 | |||
041bff1908 | |||
caa52be19e | |||
2c38a76c0a | |||
fede46e91e | |||
95ff56969e | |||
0a7d61e45e | |||
a7b2067765 | |||
427fc4cea6 | |||
9590cb9aea | |||
f982e497fe | |||
23cd5ca100 | |||
6fc944eb4a | |||
bc3caaed7d | |||
4731cc5698 | |||
6276ba58d7 | |||
c3ee389d55 | |||
0c67018eda | |||
59161400e8 | |||
c098820076 | |||
40768b2ec1 | |||
10edfe5ef6 | |||
be81716cfe | |||
3d3851d032 | |||
c459bf9397 | |||
969eaf92bc | |||
662206f32f | |||
bb9ae4d59b | |||
18964aa779 | |||
5afbcd9ffe | |||
7adf20f1ed | |||
23236ac2dc | |||
349ab639da | |||
75217ffe30 | |||
3ad8323085 | |||
362de5e40d | |||
3a2435b1e8 | |||
d995cdd501 | |||
c74ae48156 | |||
285e36e801 | |||
3ab417ec45 | |||
9049926a83 | |||
b8c99862d5 | |||
5630794fd1 | |||
bbde71463b | |||
119d019bbb | |||
9ee131a1f6 | |||
a80dac0fe1 | |||
193236f186 | |||
ea37bdb98f | |||
6f9bd14372 | |||
26d546e229 | |||
97476da964 | |||
1184558c31 | |||
20ba0a126a | |||
bd8980b8cc | |||
393da963e6 | |||
eceba274d8 | |||
cfb953e8dc | |||
81d780f081 | |||
7fdbcda1f6 | |||
7b054ed935 | |||
780c0d650c | |||
c976468d2b | |||
ee67f7e075 | |||
95d06283e4 | |||
17cf359e83 | |||
c66067da8c | |||
879dd5554d | |||
6f05978553 | |||
48e53e0edb | |||
351b8aaf84 | |||
30853dee70 | |||
5d6cf24582 | |||
088566175f | |||
f038a93796 | |||
25e65e0a98 | |||
e6455a86e0 | |||
2f75bb6083 | |||
7326fb2a7f | |||
e16843c3e4 | |||
4c0ec2ae27 | |||
d82c8cecad | |||
5992aaa5a0 | |||
f2359c75f4 | |||
5a1e741eac | |||
dfc9641e66 | |||
b99bcaa71e | |||
38cf096c0c | |||
116e421dfc | |||
d77c45630d | |||
f61b18ce13 | |||
969f63878b | |||
2ad62de9f7 | |||
265cb9b891 | |||
bd89b4cd72 | |||
9237dc565e | |||
2570163571 | |||
85d616506d | |||
50070aeddf | |||
ed52206238 | |||
899954f5a5 | |||
b9761edda0 | |||
270f17c754 | |||
be9c4de097 | |||
19e1dd2937 | |||
31b6a45ef7 | |||
cbc6796b7c | |||
7a21af570b | |||
16dff9556d | |||
5bb9df3c49 | |||
24564362ec | |||
e3c5d0dd67 | |||
0228787568 | |||
1a4e3481d5 | |||
10f4ac013e | |||
9a2b0182df | |||
d71ff26617 | |||
97ada864e1 | |||
0f67867d23 | |||
50324824c8 | |||
d161a9d145 | |||
2eed4d31dc | |||
553a97fa7a | |||
f3559bf545 | |||
0f902faa1b | |||
f5db55e96e | |||
c32e72dda6 | |||
be9f422f1f | |||
24656fa74f | |||
9123d71a7e | |||
e9c97df089 | |||
e7178d44a9 | |||
b1ee9344b8 | |||
863952b120 |
82
.gitea/workflows/ci.yml
Normal file
82
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# Automatically build the project and run any configured tests for every push
|
||||||
|
# and submitted pull request. This can help catch issues that only occur on
|
||||||
|
# certain platforms or Java versions, and provides a first line of defence
|
||||||
|
# against bad commits.
|
||||||
|
|
||||||
|
name: Test and Deploy
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master, dev ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master, dev ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
REPO_USERNAME: Altarik
|
||||||
|
REPO_PASSWORD: ${{ secrets.REPO_PASSWORD }}
|
||||||
|
DISCORD_PUB_ID: ${{ secrets.DISCORD_PUB_ID }}
|
||||||
|
DISCORD_PUB_TOKEN: ${{ secrets.DISCORD_PUB_TOKEN }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
java: [ '17' ]
|
||||||
|
os: [ ubuntu-latest ]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: validate gradle wrapper
|
||||||
|
uses: https://github.com/gradle/wrapper-validation-action@v1
|
||||||
|
- name: setup jdk ${{ matrix.Java }}
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: ${{ matrix.java }}
|
||||||
|
distribution: 'oracle'
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gradle-
|
||||||
|
- name: make gradle wrapper executable
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
run: |
|
||||||
|
chmod +x ./gradlew
|
||||||
|
touch local.properties
|
||||||
|
- name: build
|
||||||
|
run: ./gradlew build # compile classes, testClasses, assemble in jar and javadocJar, and then test
|
||||||
|
deploy:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
java: [ '17' ]
|
||||||
|
os: [ ubuntu-latest ]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
if: ${{ gitea.ref == 'refs/heads/master' && gitea.event_name == 'push' }}
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: validate gradle wrapper
|
||||||
|
uses: https://github.com/gradle/wrapper-validation-action@v1
|
||||||
|
- name: setup jdk ${{ matrix.Java }}
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: ${{ matrix.java }}
|
||||||
|
distribution: 'oracle'
|
||||||
|
- name: make gradle wrapper executable
|
||||||
|
if: ${{ runner.os != 'Windows' }}
|
||||||
|
run: |
|
||||||
|
chmod +x ./gradlew
|
||||||
|
touch local.properties
|
||||||
|
- name: deploy
|
||||||
|
run: ./gradlew publish
|
||||||
|
- name: create tag
|
||||||
|
run: ./gradlew createTag
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
- name: Report to Discord
|
||||||
|
run: ./gradlew reportToDiscord
|
||||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -3,6 +3,11 @@ build/
|
|||||||
!gradle/wrapper/gradle-wrapper.jar
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
!**/src/main/**/build/
|
!**/src/main/**/build/
|
||||||
!**/src/test/**/build/
|
!**/src/test/**/build/
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
run/*
|
||||||
|
**/run
|
||||||
|
**/logs
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
### IntelliJ IDEA ###
|
||||||
.idea
|
.idea
|
||||||
|
51
Core/src/main/java/fr/altarik/toolbox/core/Registry.java
Normal file
51
Core/src/main/java/fr/altarik/toolbox/core/Registry.java
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package fr.altarik.toolbox.core;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public abstract class Registry<T, U> {
|
||||||
|
|
||||||
|
protected final HashMap<T, U> registry = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a set view of the keys contained in the registry.
|
||||||
|
* @return An iterator view of the keys contained in the registry
|
||||||
|
*/
|
||||||
|
public Iterator<T> keySet() {
|
||||||
|
return registry.keySet().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this registry contains the key.
|
||||||
|
* @param key the key whose presence in the registry is to be tested
|
||||||
|
* @return Returns true if this registry contains the key, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean containsKey(T key) {
|
||||||
|
return registry.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the containing value represented by a specific key, or {@code null} if the registry do not contain the key
|
||||||
|
* @param key the key whose associated value is to be returned
|
||||||
|
* @return The value to which the specified key is represented, or {@code null} if the registry do not contain the key
|
||||||
|
*/
|
||||||
|
public @Nullable U getValue(T key) {
|
||||||
|
return registry.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the specified key is not already associated with a value, associate it with the given value.
|
||||||
|
* @param key Key to which the specified value is to be associated
|
||||||
|
* @param value value to be associated with the specified key
|
||||||
|
* @throws NullPointerException if key or value is null
|
||||||
|
*/
|
||||||
|
public void register(@NotNull T key, @NotNull U value) {
|
||||||
|
registry.putIfAbsent(Objects.requireNonNull(key), Objects.requireNonNull(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
|||||||
|
package fr.altarik.toolbox.core.builder;
|
||||||
|
|
||||||
|
public class EmptyCollectionException extends NullPointerException {
|
||||||
|
|
||||||
|
public EmptyCollectionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package fr.altarik.toolbox.core.builder;
|
||||||
|
|
||||||
|
public interface IBuilder<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the builders parameters into T object
|
||||||
|
* @return The created objects thanks to given parameters
|
||||||
|
* @throws Exception if any error occur during creation of the built object
|
||||||
|
*/
|
||||||
|
T build() throws Exception;
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package fr.altarik.toolbox.core.builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder parameter, for more flexibility
|
||||||
|
* @param <T> the parameter type
|
||||||
|
* @see OptionalParamBuilder
|
||||||
|
* @see RequiredParamBuilder
|
||||||
|
* @see RequiredCollectionParameterBuilder
|
||||||
|
*/
|
||||||
|
public interface IParamBuilder<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the given object, may return {@link NullPointerException} depending on the policy of implemented class
|
||||||
|
* @return the parameter given by {@link IParamBuilder#set(Object)}
|
||||||
|
* @throws NullPointerException may throw this error depending on the policy of implemented class
|
||||||
|
*/
|
||||||
|
T get() throws NullPointerException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change/insert the value of the parameter
|
||||||
|
* @param parameter the given parameter
|
||||||
|
*/
|
||||||
|
void set(T parameter);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package fr.altarik.toolbox.core.builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doesn't throw a {@link NullPointerException} when using {@link IParamBuilder#get()} in any case
|
||||||
|
* @param <T> the returned type
|
||||||
|
* @see IParamBuilder
|
||||||
|
*/
|
||||||
|
public class OptionalParamBuilder<T> implements IParamBuilder<T> {
|
||||||
|
|
||||||
|
private T parameter;
|
||||||
|
|
||||||
|
public OptionalParamBuilder(T param) {
|
||||||
|
this.parameter = param;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public T get() throws NullPointerException {
|
||||||
|
return parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(T parameter) {
|
||||||
|
this.parameter = parameter;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package fr.altarik.toolbox.core.builder;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mostly same as {@link RequiredParamBuilder} but for list
|
||||||
|
* @param <E> The type contained in the collection
|
||||||
|
* @param <T> The returned type
|
||||||
|
*/
|
||||||
|
public class RequiredCollectionParameterBuilder<E, T extends Collection<E>> implements IParamBuilder<T> {
|
||||||
|
|
||||||
|
private final T collection;
|
||||||
|
private final boolean canBeEmpty;
|
||||||
|
|
||||||
|
public RequiredCollectionParameterBuilder(@NotNull T collection, boolean canBeEmpty) {
|
||||||
|
this.collection = Objects.requireNonNull(collection);
|
||||||
|
this.canBeEmpty = canBeEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Return the list, if not empty</p>
|
||||||
|
* <p>If empty, return the collection if {@code canBeEmpty} if true, otherwise throw a {@link NullPointerException}</p>
|
||||||
|
* @return the collection
|
||||||
|
* @throws NullPointerException if collection is empty and {@code canBeEmpty} is false
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T get() throws NullPointerException {
|
||||||
|
if(canBeEmpty) {
|
||||||
|
return collection;
|
||||||
|
} else if(!collection.isEmpty()) {
|
||||||
|
return collection;
|
||||||
|
} else {
|
||||||
|
throw new EmptyCollectionException("Collection cannot be empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(T parameter) {
|
||||||
|
throw new UnsupportedOperationException("Use `add` in place of `set` for RequiredCollectionParameterBuilder");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an element to the collection
|
||||||
|
* @param element element to add to the list
|
||||||
|
*/
|
||||||
|
public void add(E element) {
|
||||||
|
collection.add(element);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package fr.altarik.toolbox.core.builder;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw a {@link NullPointerException} when using {@link IParamBuilder#get()} if the parameter doesn't have been initialized
|
||||||
|
* @param <T> the returned type
|
||||||
|
*/
|
||||||
|
public class RequiredParamBuilder<T> implements IParamBuilder<T> {
|
||||||
|
|
||||||
|
private T parameter;
|
||||||
|
|
||||||
|
public RequiredParamBuilder(T parameter) {
|
||||||
|
this.parameter = parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequiredParamBuilder() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get() {
|
||||||
|
return Objects.requireNonNull(parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(T parameter) {
|
||||||
|
this.parameter = parameter;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package fr.altarik.toolbox.core.command;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
|
import net.minecraft.server.command.ServerCommandSource;
|
||||||
|
|
||||||
|
public abstract class AbstractCommand implements Command {
|
||||||
|
|
||||||
|
protected final ServerCommandSource source;
|
||||||
|
protected final CommandContext<ServerCommandSource> context;
|
||||||
|
|
||||||
|
protected AbstractCommand(CommandContext<ServerCommandSource> c) {
|
||||||
|
this.context = c;
|
||||||
|
this.source = c.getSource();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package fr.altarik.toolbox.core.command;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
|
||||||
|
public interface Command {
|
||||||
|
|
||||||
|
int run() throws CommandSyntaxException;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package fr.altarik.toolbox.core.config;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code "inspired" from <a href="https://github.com/CaffeineMC/sodium-fabric/blob/dev/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java">
|
||||||
|
* https://github.com/CaffeineMC/sodium-fabric/blob/dev/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java
|
||||||
|
* </a>
|
||||||
|
*/
|
||||||
|
public class ConfigI {
|
||||||
|
|
||||||
|
protected static final Gson GSON = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).setPrettyPrinting().excludeFieldsWithModifiers(Modifier.PROTECTED, Modifier.PRIVATE).create();
|
||||||
|
|
||||||
|
protected Path configPath;
|
||||||
|
|
||||||
|
protected static Path getConfigPath(Path configPath, String name) {
|
||||||
|
return configPath.resolve(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends ConfigI> T load(Path configPath, String name, Class<T> clazz) throws IOException, JsonSyntaxException, JsonIOException {
|
||||||
|
Path path = getConfigPath(configPath, name);
|
||||||
|
|
||||||
|
T file;
|
||||||
|
|
||||||
|
if(Files.exists(path)) {
|
||||||
|
FileReader reader = new FileReader(path.toFile());
|
||||||
|
file = GSON.fromJson(reader, clazz);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
file = clazz.getConstructor().newInstance();
|
||||||
|
} catch (InstantiationException | InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.configPath = path;
|
||||||
|
|
||||||
|
file.writeChanges();
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeChanges() throws IOException {
|
||||||
|
Path dir = this.configPath.getParent();
|
||||||
|
if(!Files.exists(dir)) {
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
} else if (!Files.isDirectory(dir)) {
|
||||||
|
throw new IOException("Not a directory: " + dir);
|
||||||
|
}
|
||||||
|
// Use a temporary location next to the config's final destination to replace it atomically
|
||||||
|
// Path tempPath = this.configPath.resolveSibling(this.configPath.getFileName() + ".tmp");
|
||||||
|
Files.writeString(this.configPath, GSON.toJson(this));
|
||||||
|
// Files.copy(tempPath, this.configPath, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
// Files.delete(tempPath);
|
||||||
|
// commented because throws an error on windows each time if the file already exist
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package fr.altarik.toolbox.core.data;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class DataTracker {
|
||||||
|
|
||||||
|
private final Map<TrackedData, String> trackedData;
|
||||||
|
|
||||||
|
public DataTracker() {
|
||||||
|
this.trackedData = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startTracking(TrackedData data) {
|
||||||
|
String v = trackedData.get(data);
|
||||||
|
if(v == null) {
|
||||||
|
trackedData.put(data, data.defaultValue());
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Data " + data.name() + " has already been initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOrDefault(TrackedData data) {
|
||||||
|
return Objects.requireNonNull(trackedData.get(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(TrackedData data, String value) {
|
||||||
|
String v = trackedData.get(data);
|
||||||
|
if(v != null) {
|
||||||
|
trackedData.put(data, value);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Data " + data.name() + " is not tracked, please initialize it with DataTracker#startTracking(TrackedData, String) first");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator<TrackedData> getTrackedDataIterator() {
|
||||||
|
return trackedData.keySet().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator<String> getTrackedDataValueIterator() {
|
||||||
|
return trackedData.values().iterator();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package fr.altarik.toolbox.core.data;
|
||||||
|
|
||||||
|
public record TrackedData(String name, String defaultValue) {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package fr.altarik.toolbox.core.event;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.util.ActionResult;
|
||||||
|
|
||||||
|
public interface PlayerLifecycleCallback {
|
||||||
|
|
||||||
|
Event<PlayerJoin> PLAYER_JOIN = EventFactory.createArrayBacked(PlayerJoin.class, listeners -> player -> {
|
||||||
|
for(PlayerJoin listener : listeners) {
|
||||||
|
ActionResult result = listener.onPlayerJoin(player);
|
||||||
|
if (result != ActionResult.PASS)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return ActionResult.PASS;
|
||||||
|
});
|
||||||
|
|
||||||
|
Event<PlayerLeave> PLAYER_LEAVE = EventFactory.createArrayBacked(PlayerLeave.class, listeners -> player -> {
|
||||||
|
for(PlayerLeave listener : listeners) {
|
||||||
|
ActionResult result = listener.onPlayerLeave(player);
|
||||||
|
if (result != ActionResult.PASS)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return ActionResult.PASS;
|
||||||
|
});
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface PlayerJoin {
|
||||||
|
ActionResult onPlayerJoin(ServerPlayerEntity player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface PlayerLeave {
|
||||||
|
ActionResult onPlayerLeave(ServerPlayerEntity player);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package fr.altarik.toolbox.core.mixin;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.core.event.PlayerLifecycleCallback;
|
||||||
|
import net.minecraft.network.ClientConnection;
|
||||||
|
import net.minecraft.server.PlayerManager;
|
||||||
|
import net.minecraft.server.network.ConnectedClientData;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(PlayerManager.class)
|
||||||
|
public class PlayerJoinEvent {
|
||||||
|
|
||||||
|
@Inject(method = "onPlayerConnect", at = @At(value = "RETURN"))
|
||||||
|
private void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) {
|
||||||
|
PlayerLifecycleCallback.PLAYER_JOIN.invoker().onPlayerJoin(player);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package fr.altarik.toolbox.core.mixin;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.core.event.PlayerLifecycleCallback;
|
||||||
|
import net.minecraft.server.network.ServerPlayNetworkHandler;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(ServerPlayNetworkHandler.class)
|
||||||
|
public class PlayerLeaveEvent {
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public ServerPlayerEntity player;
|
||||||
|
|
||||||
|
@Inject(at = @At(value = "HEAD"), method = "onDisconnected")
|
||||||
|
private void onPlayerQuit(Text reason, CallbackInfo ci) {
|
||||||
|
PlayerLifecycleCallback.PLAYER_LEAVE.invoker().onPlayerLeave(player);
|
||||||
|
}
|
||||||
|
}
|
14
Core/src/main/resources/Core.mixins.json
Normal file
14
Core/src/main/resources/Core.mixins.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "fr.altarik.toolbox.core.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_17",
|
||||||
|
"mixins": [
|
||||||
|
"PlayerJoinEvent",
|
||||||
|
"PlayerLeaveEvent"
|
||||||
|
],
|
||||||
|
"verbose": false,
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Core/src/main/resources/assets/core/icon.png
Normal file
BIN
Core/src/main/resources/assets/core/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 323 KiB |
39
Core/src/main/resources/fabric.mod.json
Normal file
39
Core/src/main/resources/fabric.mod.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "toolbox-core",
|
||||||
|
"version": "${version}",
|
||||||
|
"name": "Altarik Toolbox Core",
|
||||||
|
"description": "Dependency of some of altarik toolbox mods",
|
||||||
|
"authors": [
|
||||||
|
"Altarik"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"contact": {
|
||||||
|
"homepage": "https://altarik.fr"
|
||||||
|
},
|
||||||
|
"license": "Altarik @ All-Rights-Reserved ",
|
||||||
|
"icon": "assets/core/icon.png",
|
||||||
|
"environment": "*",
|
||||||
|
"entrypoints": {
|
||||||
|
"main": []
|
||||||
|
},
|
||||||
|
"mixins": [
|
||||||
|
"Core.mixins.json"
|
||||||
|
],
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": "^${loaderVersion}",
|
||||||
|
"fabric-api": "*",
|
||||||
|
"minecraft": "${minecraftVersion}",
|
||||||
|
"java": ">=17"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"modmenu": {
|
||||||
|
"badges": [ "library" ],
|
||||||
|
"parent": {
|
||||||
|
"parent": "toolbox"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
Core/src/test/java/BuilderImpl.java
Normal file
38
Core/src/test/java/BuilderImpl.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import fr.altarik.toolbox.core.builder.IBuilder;
|
||||||
|
import fr.altarik.toolbox.core.builder.RequiredCollectionParameterBuilder;
|
||||||
|
import fr.altarik.toolbox.core.builder.RequiredParamBuilder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BuilderImpl implements IBuilder<BuilderResult> {
|
||||||
|
|
||||||
|
private final RequiredCollectionParameterBuilder<String, List<String>> collection;
|
||||||
|
private final RequiredParamBuilder<Integer> numberOfSentences;
|
||||||
|
|
||||||
|
private BuilderImpl(boolean canBeEmpty) {
|
||||||
|
this.collection = new RequiredCollectionParameterBuilder<>(new ArrayList<>(), canBeEmpty);
|
||||||
|
this.numberOfSentences = new RequiredParamBuilder<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BuilderImpl addSentence(String sentence) {
|
||||||
|
collection.add(sentence);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BuilderImpl numberOfSentence(int i) {
|
||||||
|
this.numberOfSentences.set(i);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BuilderImpl builder(boolean canBeEmpty) {
|
||||||
|
return new BuilderImpl(canBeEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BuilderResult build() {
|
||||||
|
return new BuilderResult(collection.get(), numberOfSentences.get());
|
||||||
|
}
|
||||||
|
}
|
5
Core/src/test/java/BuilderResult.java
Normal file
5
Core/src/test/java/BuilderResult.java
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record BuilderResult(List<String> sentences, int numberOfSentences) {
|
||||||
|
|
||||||
|
}
|
32
Core/src/test/java/BuilderTest.java
Normal file
32
Core/src/test/java/BuilderTest.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import fr.altarik.toolbox.core.builder.EmptyCollectionException;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class BuilderTest {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void builderTest() throws Exception {
|
||||||
|
BuilderImpl builder = BuilderImpl.builder(true);
|
||||||
|
builder.addSentence("First sentence");
|
||||||
|
builder.addSentence("Second sentence");
|
||||||
|
builder.numberOfSentence(2);
|
||||||
|
BuilderResult res = builder.build();
|
||||||
|
Assertions.assertEquals(Arrays.asList("First sentence", "Second sentence"), res.sentences());
|
||||||
|
Assertions.assertEquals(res.numberOfSentences(), 2);
|
||||||
|
|
||||||
|
BuilderImpl builder1 = BuilderImpl.builder(false);
|
||||||
|
builder1.numberOfSentence(3);
|
||||||
|
Assertions.assertThrowsExactly(EmptyCollectionException.class, builder1::build);
|
||||||
|
|
||||||
|
BuilderImpl builder2 = BuilderImpl.builder(true);
|
||||||
|
builder2.numberOfSentence(3);
|
||||||
|
Assertions.assertDoesNotThrow(builder2::build);
|
||||||
|
|
||||||
|
BuilderImpl builder3 = BuilderImpl.builder(true);
|
||||||
|
Assertions.assertThrowsExactly(NullPointerException.class, builder3::build);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
40
Core/src/test/java/ConfigITest.java
Normal file
40
Core/src/test/java/ConfigITest.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import fr.altarik.toolbox.core.config.ConfigI;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class ConfigITest {
|
||||||
|
|
||||||
|
public static class ConfigClazz extends ConfigI {
|
||||||
|
|
||||||
|
public int par1 = 5;
|
||||||
|
public String par2 = "bad";
|
||||||
|
public double para3 = 3.14;
|
||||||
|
|
||||||
|
public static ConfigClazz load() throws IOException {
|
||||||
|
return load(Path.of("."), "test.json", ConfigClazz.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConfig() throws IOException {
|
||||||
|
ConfigClazz config = ConfigClazz.load();
|
||||||
|
Assertions.assertEquals(5, config.par1);
|
||||||
|
Assertions.assertEquals("bad", config.par2);
|
||||||
|
Assertions.assertEquals(3.14, config.para3);
|
||||||
|
config.par1 = 6;
|
||||||
|
config.par2 = "good";
|
||||||
|
config.para3 = 4.2;
|
||||||
|
Assertions.assertEquals(6, config.par1);
|
||||||
|
config.writeChanges();
|
||||||
|
config = ConfigClazz.load();
|
||||||
|
Assertions.assertEquals(6, config.par1);
|
||||||
|
Assertions.assertEquals("good", config.par2);
|
||||||
|
Assertions.assertEquals(4.2, config.para3);
|
||||||
|
|
||||||
|
Path.of(".").resolve("test.json").toFile().delete();
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,9 @@
|
|||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
id 'maven-publish'
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.postgresql:postgresql:42.5.0'
|
implementation 'org.postgresql:postgresql:42.6.0'
|
||||||
testImplementation 'com.google.code.gson:gson:2.10'
|
testImplementation 'com.google.code.gson:gson:2.10'
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-api:5.9.0"
|
implementation project(':Core')
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.9.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
withSourcesJar()
|
|
||||||
withJavadocJar()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
exclude 'fr/altarik/toolbox/database/**' // exclude for runner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,16 +25,11 @@ public abstract class AbstractSqlConnection implements SqlConnection {
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeConnection() {
|
@Override
|
||||||
try {
|
public void close() throws Exception {
|
||||||
if(!connection.isClosed()) {
|
if(!connection.isClosed()) {
|
||||||
connection.close();
|
connection.close();
|
||||||
connection = null;
|
connection = null;
|
||||||
}
|
|
||||||
} catch(SQLException ignored) {
|
|
||||||
// no op
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,28 @@
|
|||||||
package fr.altarik.toolbox.database;
|
package fr.altarik.toolbox.database;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.database.keyValue.KeyValueBuilder;
|
||||||
|
import fr.altarik.toolbox.database.keyValue.KeyValueTable;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
public class Connections {
|
public class Connections {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Connection object for a postgresql database server
|
* Create a new Connection object for a postgresql database server
|
||||||
* @return
|
* @return postgresql connection
|
||||||
*/
|
*/
|
||||||
public static SqlConnection newPostgresConnection(ConnectionConfig config) throws SQLException {
|
public static SqlConnection newPostgresConnection(ConnectionConfig config) throws SQLException {
|
||||||
return new PostgresConnection(config);
|
return new PostgresConnection(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new (key, value) table if not exist and use it through {@link KeyValueTable} interface
|
||||||
|
* @param connection Postgresql connection
|
||||||
|
* @param tableName name of the table to use
|
||||||
|
* @return interface to control the table
|
||||||
|
* @throws SQLException if connection is lost
|
||||||
|
*/
|
||||||
|
public static KeyValueTable newKeyValueTable(SqlConnection connection, String tableName) throws SQLException {
|
||||||
|
return KeyValueBuilder.builder().setConnection(connection).setTableName(tableName).build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import java.sql.SQLException;
|
|||||||
|
|
||||||
public class PostgresConnection extends AbstractSqlConnection {
|
public class PostgresConnection extends AbstractSqlConnection {
|
||||||
|
|
||||||
PostgresConnection(ConnectionConfig config) throws SQLException {
|
public PostgresConnection(ConnectionConfig config) throws SQLException {
|
||||||
super(config);
|
super(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,14 +3,24 @@ package fr.altarik.toolbox.database;
|
|||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
public interface SqlConnection {
|
public interface SqlConnection extends AutoCloseable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the connection to sql database
|
||||||
|
* @throws SQLException if unable to connect to database
|
||||||
|
*/
|
||||||
void connect() throws SQLException;
|
void connect() throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sql connection
|
||||||
|
* @return the connection session
|
||||||
|
*/
|
||||||
Connection getConnection();
|
Connection getConnection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconnect you to database if it has closed or lost.
|
||||||
|
* @throws SQLException if unable to reconnect you
|
||||||
|
*/
|
||||||
void checkConnection() throws SQLException;
|
void checkConnection() throws SQLException;
|
||||||
|
|
||||||
void closeConnection();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
package fr.altarik.toolbox.database.keyValue;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.core.builder.IBuilder;
|
||||||
|
import fr.altarik.toolbox.core.builder.RequiredParamBuilder;
|
||||||
|
import fr.altarik.toolbox.database.SqlConnection;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
|
||||||
|
public class KeyValueBuilder implements IBuilder<KeyValuePostgresql> {
|
||||||
|
|
||||||
|
private final RequiredParamBuilder<String> tableName;
|
||||||
|
private final RequiredParamBuilder<SqlConnection> connection;
|
||||||
|
|
||||||
|
private KeyValueBuilder() {
|
||||||
|
this.tableName = new RequiredParamBuilder<>();
|
||||||
|
this.connection = new RequiredParamBuilder<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyValueBuilder builder() {
|
||||||
|
return new KeyValueBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyValueBuilder setConnection(@NotNull SqlConnection connection) {
|
||||||
|
this.connection.set(connection);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyValueBuilder setTableName(@NotNull String tableName) {
|
||||||
|
this.tableName.set(tableName);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyValuePostgresql build() throws SQLException {
|
||||||
|
return new KeyValuePostgresql(connection.get(), tableName.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package fr.altarik.toolbox.database.keyValue;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.database.SqlConnection;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
public class KeyValuePostgresql implements KeyValueTable {
|
||||||
|
|
||||||
|
private final SqlConnection connection;
|
||||||
|
private final String tableName;
|
||||||
|
|
||||||
|
public KeyValuePostgresql(@NotNull SqlConnection connection, @NotNull String tableName) throws SQLException {
|
||||||
|
this.connection = connection;
|
||||||
|
this.tableName = tableName;
|
||||||
|
connection.checkConnection();
|
||||||
|
createTable(tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTable(String tableName) throws SQLException {
|
||||||
|
try(Statement statement = connection.getConnection().createStatement()) {
|
||||||
|
statement.executeUpdate("CREATE TABLE IF NOT EXISTS " + tableName + "(key VARCHAR(50) NOT NULL, value TEXT NOT NULL, PRIMARY KEY(key));");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getValue(String key) throws SQLException {
|
||||||
|
connection.checkConnection();
|
||||||
|
try(PreparedStatement preparedStatement = connection.getConnection().prepareStatement("SELECT value FROM " + tableName + " WHERE key=?;")) {
|
||||||
|
preparedStatement.setString(1, key);
|
||||||
|
ResultSet resultSet = preparedStatement.executeQuery();
|
||||||
|
if(resultSet.next())
|
||||||
|
return resultSet.getString(1);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertValue(String key, String value) throws SQLException {
|
||||||
|
connection.checkConnection();
|
||||||
|
try(PreparedStatement preparedStatement = connection.getConnection().prepareStatement("INSERT INTO " + tableName + "(key, value) VALUES (?, ?);")){
|
||||||
|
preparedStatement.setString(1, key);
|
||||||
|
preparedStatement.setString(2, value);
|
||||||
|
preparedStatement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateValue(String key, String value) throws SQLException {
|
||||||
|
connection.checkConnection();
|
||||||
|
try(PreparedStatement preparedStatement = connection.getConnection().prepareStatement("UPDATE " + tableName + " SET value=? WHERE key=?;")) {
|
||||||
|
preparedStatement.setString(1, value);
|
||||||
|
preparedStatement.setString(2, key);
|
||||||
|
preparedStatement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteRow(String key) throws SQLException {
|
||||||
|
connection.checkConnection();
|
||||||
|
try(PreparedStatement preparedStatement = connection.getConnection().prepareStatement("DELETE FROM " + tableName + " WHERE key=?")) {
|
||||||
|
preparedStatement.setString(1, key);
|
||||||
|
preparedStatement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void truncateTable() throws SQLException {
|
||||||
|
connection.checkConnection();
|
||||||
|
try(PreparedStatement preparedStatement = connection.getConnection().prepareStatement("TRUNCATE TABLE " + tableName)) {
|
||||||
|
preparedStatement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package fr.altarik.toolbox.database.keyValue;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Implement of a key value table, abstract the actual representation of the table and its manipulation between this interface</p>
|
||||||
|
* @see KeyValuePostgresql
|
||||||
|
*/
|
||||||
|
public interface KeyValueTable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Return the first value associated with the unique key.</p>
|
||||||
|
*
|
||||||
|
* @param key String key of associated to the value
|
||||||
|
* @return value associated with the key
|
||||||
|
* @throws SQLException if connection is lost
|
||||||
|
*/
|
||||||
|
@Nullable String getValue(String key) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Insert a new value in the table, associated with key</p>
|
||||||
|
*
|
||||||
|
* @param key String key which will be associated with the value, is unique
|
||||||
|
* @param value String value which will be stored in the database
|
||||||
|
* @throws SQLException if connection is lost or if {@code key} is not unique (already exist in database)
|
||||||
|
*/
|
||||||
|
void insertValue(String key, String value) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Update value column of the row associated with the key by {@code value}</p>
|
||||||
|
* <p>If {@code key} doesn't exist in table, will update no row without warning</p>
|
||||||
|
* @param key String key which will is associated with the value, is unique
|
||||||
|
* @param value new value
|
||||||
|
* @throws SQLException if connection is lost
|
||||||
|
*/
|
||||||
|
void updateValue(String key, String value) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Delete row with having {@code key} as unique key</p>
|
||||||
|
* <p>If key doesn't exist in database, will delete no row without warning</p>
|
||||||
|
* @param key the key of the row to delete
|
||||||
|
* @throws SQLException if connection is lost
|
||||||
|
*/
|
||||||
|
void deleteRow(String key) throws SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will delete every data inside the table
|
||||||
|
* @throws SQLException if connection is lost
|
||||||
|
*/
|
||||||
|
void truncateTable() throws SQLException;
|
||||||
|
|
||||||
|
}
|
BIN
Database/src/main/resources/assets/database/icon.png
Normal file
BIN
Database/src/main/resources/assets/database/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 323 KiB |
38
Database/src/main/resources/fabric.mod.json
Normal file
38
Database/src/main/resources/fabric.mod.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "toolbox-database",
|
||||||
|
"version": "${version}",
|
||||||
|
"name": "Altarik Toolbox Database",
|
||||||
|
"description": "A set of sql tools",
|
||||||
|
"authors": [
|
||||||
|
"Altarik"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"contact": {
|
||||||
|
"homepage": "https://altarik.fr"
|
||||||
|
},
|
||||||
|
"license": "Altarik @ All-Rights-Reserved ",
|
||||||
|
"icon": "assets/database/icon.png",
|
||||||
|
"environment": "*",
|
||||||
|
"entrypoints": {
|
||||||
|
"main": []
|
||||||
|
},
|
||||||
|
"mixins": [],
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": "^${loaderVersion}",
|
||||||
|
"fabric-api": "*",
|
||||||
|
"minecraft": "${minecraftVersion}",
|
||||||
|
"java": ">=17",
|
||||||
|
"toolbox-core": "${version}"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"modmenu": {
|
||||||
|
"badges": [ "library" ],
|
||||||
|
"parent": {
|
||||||
|
"parent": "toolbox"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,7 @@ class ConnectionTest {
|
|||||||
try(PreparedStatement statement = connection.getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS toolbox(id SERIAL, PRIMARY KEY (id));")) {
|
try(PreparedStatement statement = connection.getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS toolbox(id SERIAL, PRIMARY KEY (id));")) {
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
|
connection.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
package fr.altarik.toolbox.database.keyValue;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import fr.altarik.toolbox.database.ConnectionConfig;
|
||||||
|
import fr.altarik.toolbox.database.Connections;
|
||||||
|
import fr.altarik.toolbox.database.SqlConnection;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class KeyValueTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void tableTest() {
|
||||||
|
System.out.println("Hello");
|
||||||
|
assertDoesNotThrow(() -> {InputStream configInput = getResource("config.yml");
|
||||||
|
String configStr = new BufferedReader(new InputStreamReader(Objects.requireNonNull(configInput)))
|
||||||
|
.lines().collect(Collectors.joining("\n"));
|
||||||
|
Gson gson = new Gson();
|
||||||
|
ConnectionConfig config = gson.fromJson(configStr, ConnectionConfig.class);
|
||||||
|
try(SqlConnection connection = Connections.newPostgresConnection(config)) {
|
||||||
|
KeyValueTable keyValueTable = Connections.newKeyValueTable(connection, "toolbox_keyvalue");
|
||||||
|
keyValueTable.truncateTable();
|
||||||
|
keyValueTable.insertValue("location", "here");
|
||||||
|
keyValueTable.insertValue("experience", "5");
|
||||||
|
assertEquals("here", keyValueTable.getValue("location"));
|
||||||
|
assertEquals("5", keyValueTable.getValue("experience"));
|
||||||
|
keyValueTable.updateValue("location", "Elsewhere");
|
||||||
|
assertEquals("Elsewhere", keyValueTable.getValue("location"));
|
||||||
|
assertEquals("5", keyValueTable.getValue("experience"));
|
||||||
|
keyValueTable.updateValue("experience", "10");
|
||||||
|
assertEquals("Elsewhere", keyValueTable.getValue("location"));
|
||||||
|
assertEquals("10", keyValueTable.getValue("experience"));
|
||||||
|
keyValueTable.deleteRow("experience");
|
||||||
|
assertEquals("Elsewhere", keyValueTable.getValue("location"));
|
||||||
|
assertNull(keyValueTable.getValue("experience"));
|
||||||
|
keyValueTable.truncateTable();
|
||||||
|
assertNull(keyValueTable.getValue("location"));
|
||||||
|
assertNull(keyValueTable.getValue("experience"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 08/06/2023 Move to Core module in a toolkit class
|
||||||
|
private InputStream getResource(String resourcePath) {
|
||||||
|
try {
|
||||||
|
URL url = this.getClass().getClassLoader().getResource(resourcePath);
|
||||||
|
if(url == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
URLConnection connection = url.openConnection();
|
||||||
|
connection.setUseCaches(false);
|
||||||
|
return connection.getInputStream();
|
||||||
|
} catch (IOException e){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,5 +3,5 @@
|
|||||||
"port": 5432,
|
"port": 5432,
|
||||||
"database": "postgres",
|
"database": "postgres",
|
||||||
"username": "postgres",
|
"username": "postgres",
|
||||||
"password": "root"
|
"password": "Vaubadon1"
|
||||||
}
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
package fr.altarik.toolbox.pagination;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.pagination.api.PageIndexOutOfBoundException;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.text.ClickEvent;
|
||||||
|
import net.minecraft.text.MutableText;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.util.Formatting;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class PaginatedContent {
|
||||||
|
|
||||||
|
private final List<Page> pages;
|
||||||
|
private final Text header;
|
||||||
|
|
||||||
|
public PaginatedContent(String header, String content) {
|
||||||
|
this.header = buildHeader(header);
|
||||||
|
pages = new ArrayList<>();
|
||||||
|
List<String> secondSplit = new ArrayList<>();
|
||||||
|
for(String elem : Stream.of(content.split("\n")).collect(Collectors.toCollection(ArrayList::new))) {
|
||||||
|
if(elem.length() > 50) {
|
||||||
|
secondSplit.add(elem.substring(0, 50));
|
||||||
|
secondSplit.add(elem.substring(51, elem.length() - 1));
|
||||||
|
} else {
|
||||||
|
secondSplit.add(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<Text> currentPage = new ArrayList<>();
|
||||||
|
for(String elem : secondSplit) {
|
||||||
|
if(!elem.isEmpty()) {
|
||||||
|
currentPage.add(Text.literal(elem));
|
||||||
|
}
|
||||||
|
if(currentPage.size() == 8 || elem.isEmpty()) {
|
||||||
|
pages.add(new Page(currentPage));
|
||||||
|
currentPage = new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pages.add(new Page(currentPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaginatedContent(@Nullable Text header, List<Text> content) {
|
||||||
|
this.header = buildHeader(header);
|
||||||
|
this.pages = new ArrayList<>();
|
||||||
|
List<Text> currentPage = new ArrayList<>();
|
||||||
|
for(Text elem : content) {
|
||||||
|
if(elem != null)
|
||||||
|
currentPage.add(elem);
|
||||||
|
if(currentPage.size() == 8 || elem == null) {
|
||||||
|
pages.add(new Page(currentPage));
|
||||||
|
currentPage = new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pages.add(new Page(currentPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Text buildHeader(@Nullable String header) {
|
||||||
|
int numberOfEq = (50 - (header != null ? header.length() : 0)) / 2;
|
||||||
|
return Text.literal("=".repeat(numberOfEq) + " " + header + " " + "=".repeat(numberOfEq));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Text buildHeader(@Nullable Text header) {
|
||||||
|
int numberOfEq = (50 - (header != null ? header.getString().length() : 0)) / 2;
|
||||||
|
return Text.literal("=".repeat(numberOfEq) + " ").append(header).append(" " + "=".repeat(numberOfEq));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void display(ServerPlayerEntity playerEntity, int page) throws PageIndexOutOfBoundException {
|
||||||
|
if(page >= this.pages.size()) {
|
||||||
|
throw new PageIndexOutOfBoundException("api.pagination.page_higher_than_expected", this.pages.size(), (page + 1));
|
||||||
|
} else if(page < 0) {
|
||||||
|
throw new PageIndexOutOfBoundException("api.pagination.page_lower_than_0");
|
||||||
|
} else {
|
||||||
|
playerEntity.sendMessage(header);
|
||||||
|
for(Text s : pages.get(page).lines) {
|
||||||
|
playerEntity.sendMessage(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
playerEntity.sendMessage(buildFooter(page));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Text buildFooter(int page) {
|
||||||
|
String strPage = String.valueOf(page + 1);
|
||||||
|
int numberOfEq = (46 - strPage.length()) / 2;
|
||||||
|
MutableText left = Text.literal(" <").styled(
|
||||||
|
style -> style
|
||||||
|
.withColor(Formatting.YELLOW)
|
||||||
|
.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/table page " + (page - 1)))
|
||||||
|
);
|
||||||
|
MutableText middle = Text.literal(" " + strPage + " ").styled(style -> style.withColor(Formatting.RESET));
|
||||||
|
MutableText right = Text.literal("> ").styled(
|
||||||
|
style -> style
|
||||||
|
.withColor(Formatting.YELLOW)
|
||||||
|
.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/table page " + (page + 1)))
|
||||||
|
);
|
||||||
|
return Text.literal("=".repeat(numberOfEq))
|
||||||
|
.append(left)
|
||||||
|
.append(middle)
|
||||||
|
.append(right)
|
||||||
|
.append(
|
||||||
|
Text.literal("=".repeat(numberOfEq))
|
||||||
|
.styled(style -> style.withColor(Formatting.RESET))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Page(List<Text> lines) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package fr.altarik.toolbox.pagination;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.pagination.api.PaginationApi;
|
||||||
|
import fr.altarik.toolbox.pagination.api.PaginationApiImpl;
|
||||||
|
import fr.altarik.toolbox.pagination.command.CommandsRegister;
|
||||||
|
import net.fabricmc.api.ModInitializer;
|
||||||
|
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class Pagination implements ModInitializer {
|
||||||
|
|
||||||
|
private static Pagination instance;
|
||||||
|
private final PaginationApi api;
|
||||||
|
|
||||||
|
public Pagination() {
|
||||||
|
instance = this;
|
||||||
|
this.api = new PaginationApiImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitialize() {
|
||||||
|
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> new CommandsRegister(this).register(dispatcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull PaginationApi getApi() {
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static @NotNull Pagination getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package fr.altarik.toolbox.pagination.api;
|
||||||
|
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
|
||||||
|
public class PageIndexOutOfBoundException extends Exception {
|
||||||
|
|
||||||
|
private final Text text;
|
||||||
|
|
||||||
|
public PageIndexOutOfBoundException(String s) {
|
||||||
|
this.text = Text.translatable(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageIndexOutOfBoundException(String s, int size, int currentPage) {
|
||||||
|
this.text = Text.translatable(s, size, currentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Text getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package fr.altarik.toolbox.pagination.api;
|
||||||
|
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface PaginationApi {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a pagination table for player, content is separated into multiple pages.<br>
|
||||||
|
* You can separate yourself content between two pages by adding *\n\n*.</p>
|
||||||
|
* <p>Content have a time-to-live of 15 minutes (18,000 ticks)</p>
|
||||||
|
* @param playerEntity The player who will be able to interact and see the paginated message
|
||||||
|
* @param content Content you want to paginate
|
||||||
|
* @param header Header/title you want to add to every page, empty space is filled with "=".
|
||||||
|
* <p>Special values are:
|
||||||
|
* <ul><li><b>null</b> if you doesn't want to add a header</li>
|
||||||
|
* <li><b>empty String</b> if you want just the header to be filled only with "="</li></ul>
|
||||||
|
* @param display true if you want the message to be displayed now, false otherwise if you want to display the
|
||||||
|
* message yourself
|
||||||
|
* @throws IllegalArgumentException if one of its conditions is met: <ol>
|
||||||
|
* <li><b>header</b> length is more than 50 characters</li>
|
||||||
|
* <li><b>content</b> is empty/blank</li>
|
||||||
|
* <li><b>playerEntity</b> or <b>content</b> are null</li>
|
||||||
|
* </ol>
|
||||||
|
*/
|
||||||
|
void createTable(ServerPlayerEntity playerEntity, String content, String header, boolean display);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Create a pagination table for player the same way than
|
||||||
|
* {@link PaginationApi#createTable(ServerPlayerEntity, String, String, boolean)},
|
||||||
|
* content is separated into multiple pages.<br />
|
||||||
|
* You can separate yourself content between 2 pages by adding a null instance of Text in content list.</p>
|
||||||
|
* <p>Content have a time-to-live of 15 minutes (18,000 ticks)</p>
|
||||||
|
* @param playerEntity The player who will be able to interact and see the paginated message
|
||||||
|
* @param content Content you want to paginate
|
||||||
|
* @param header header/title you want to add to every page, empty space is filled with "=".
|
||||||
|
* <p>Special values are:</p>
|
||||||
|
* <ul><li><b>null</b> if you doesn't want to add a header</li>
|
||||||
|
* <li><b>Empty text</b>if you want just the header to be filled only with "="</li></ul>
|
||||||
|
* @param display true if you want the message to be displayed now, false otherwise if you want to display the
|
||||||
|
* message yourself
|
||||||
|
* @throws IllegalArgumentException if one of its conditions is met: <ol>
|
||||||
|
* <li><b>header</b> length is more than 50 characters</li>
|
||||||
|
* <li><b>content</b> is empty/blank</li>
|
||||||
|
* <li><b>playerEntity</b> or <b>content</b> are null</li>
|
||||||
|
* </ol>
|
||||||
|
* @see Text#empty()
|
||||||
|
*/
|
||||||
|
void createTable(ServerPlayerEntity playerEntity, List<Text> content, @Nullable Text header, boolean display);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the given page for the given player
|
||||||
|
* @param player display the content of this player
|
||||||
|
* @param page display this page
|
||||||
|
* @throws IllegalArgumentException if page is invalid
|
||||||
|
* @throws NullPointerException if player is null or paginated content for the player doesn't exist (or have expired)
|
||||||
|
* @see fr.altarik.toolbox.pagination.PaginatedContent#display(ServerPlayerEntity, int)
|
||||||
|
*/
|
||||||
|
void display(ServerPlayerEntity player, int page) throws PageIndexOutOfBoundException;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package fr.altarik.toolbox.pagination.api;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.pagination.PaginatedContent;
|
||||||
|
import fr.altarik.toolbox.pagination.precondition.ContentCondition;
|
||||||
|
import fr.altarik.toolbox.pagination.precondition.HeaderCondition;
|
||||||
|
import fr.altarik.toolbox.pagination.precondition.NullPlayerCondition;
|
||||||
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.util.Pair;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class PaginationApiImpl implements PaginationApi {
|
||||||
|
/**
|
||||||
|
* Integer represent relative tts of the paginated content, decreased by 1 every seconds
|
||||||
|
*/
|
||||||
|
public final Map<ServerPlayerEntity, Pair<Integer, PaginatedContent>> paginatedContent = new HashMap<>();
|
||||||
|
private final Predicate<ServerPlayerEntity> playerCondition = new NullPlayerCondition().negate();
|
||||||
|
private final Predicate<String> headerCondition = new HeaderCondition().negate();
|
||||||
|
private final Predicate<String> contentCondition = new ContentCondition().negate();
|
||||||
|
|
||||||
|
public PaginationApiImpl() {
|
||||||
|
ServerTickEvents.START_SERVER_TICK.register(this::serverTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createTable(ServerPlayerEntity playerEntity, String content, String header, boolean display) {
|
||||||
|
if(playerCondition.test(playerEntity) || headerCondition.test(header) || contentCondition.test(content)) {
|
||||||
|
throw new IllegalArgumentException("Preconditions aren't satisfied");
|
||||||
|
}
|
||||||
|
PaginatedContent paginatedContent1 = new PaginatedContent(header, content);
|
||||||
|
storeAndDisplay(playerEntity, paginatedContent1, display);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createTable(ServerPlayerEntity playerEntity, List<Text> content, @Nullable Text header, boolean display) {
|
||||||
|
if(playerCondition.test(playerEntity)) {
|
||||||
|
throw new IllegalArgumentException("Preconditions aren't satisfied");
|
||||||
|
}
|
||||||
|
PaginatedContent paginatedContent1 = new PaginatedContent(header, content);
|
||||||
|
storeAndDisplay(playerEntity, paginatedContent1, display);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeAndDisplay(ServerPlayerEntity playerEntity, PaginatedContent paginatedContent1, boolean display) {
|
||||||
|
paginatedContent.put(playerEntity, new Pair<>(18000, paginatedContent1));
|
||||||
|
if(display) {
|
||||||
|
try {
|
||||||
|
paginatedContent1.display(playerEntity, 0);
|
||||||
|
} catch (PageIndexOutOfBoundException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void display(ServerPlayerEntity player, int page) throws PageIndexOutOfBoundException {
|
||||||
|
if(player == null)
|
||||||
|
throw new NullPointerException("Player is null");
|
||||||
|
Pair<Integer, PaginatedContent> pair = paginatedContent.get(player);
|
||||||
|
if(pair == null)
|
||||||
|
throw new NullPointerException("No paginated page for player " + player.getCustomName());
|
||||||
|
pair.getRight().display(player, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void serverTick(MinecraftServer server) {
|
||||||
|
List<ServerPlayerEntity> toRemove = new ArrayList<>();
|
||||||
|
for(Map.Entry<ServerPlayerEntity, Pair<Integer, PaginatedContent>> content : paginatedContent.entrySet()) {
|
||||||
|
if(content.getValue().getLeft() == 0) {
|
||||||
|
toRemove.add(content.getKey());
|
||||||
|
} else {
|
||||||
|
content.getValue().setLeft(content.getValue().getLeft() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(ServerPlayerEntity player : toRemove) {
|
||||||
|
paginatedContent.remove(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package fr.altarik.toolbox.pagination.command;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
|
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||||
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
|
||||||
|
import fr.altarik.toolbox.pagination.Pagination;
|
||||||
|
import fr.altarik.toolbox.pagination.api.PageIndexOutOfBoundException;
|
||||||
|
import fr.altarik.toolbox.pagination.api.PaginationApi;
|
||||||
|
import net.minecraft.server.command.ServerCommandSource;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static net.minecraft.server.command.CommandManager.argument;
|
||||||
|
import static net.minecraft.server.command.CommandManager.literal;
|
||||||
|
|
||||||
|
public class CommandsRegister {
|
||||||
|
|
||||||
|
private final PaginationApi api;
|
||||||
|
|
||||||
|
public CommandsRegister(Pagination instance) {
|
||||||
|
this.api = instance.getApi();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(CommandDispatcher<ServerCommandSource> dispatcher) {
|
||||||
|
dispatcher.register(literal("table")
|
||||||
|
.then(literal("page")
|
||||||
|
.then(argument("page", IntegerArgumentType.integer())
|
||||||
|
.executes(this::selectPageCommand)
|
||||||
|
)
|
||||||
|
).then(literal("test")
|
||||||
|
.requires(source -> source.isExecutedByPlayer() && source.hasPermissionLevel(3))
|
||||||
|
.executes(this::testPageCommand)
|
||||||
|
).then(literal("testText")
|
||||||
|
.requires(source -> source.isExecutedByPlayer() && source.hasPermissionLevel(3))
|
||||||
|
.executes(this::testPageTextCommand)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply a debug command
|
||||||
|
*/
|
||||||
|
private int testPageCommand(CommandContext<ServerCommandSource> context) {
|
||||||
|
api.createTable(context.getSource().getPlayer(), """
|
||||||
|
first line, string version
|
||||||
|
Second line
|
||||||
|
|
||||||
|
second page
|
||||||
|
dqdq
|
||||||
|
dqdqd
|
||||||
|
qdqdq
|
||||||
|
dqdq
|
||||||
|
dqdq
|
||||||
|
dqdq
|
||||||
|
dqdqd
|
||||||
|
third page
|
||||||
|
dqdqd
|
||||||
|
dqdqd
|
||||||
|
d""", "My header", true);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int testPageTextCommand(CommandContext<ServerCommandSource> context) {
|
||||||
|
List<Text> content = new ArrayList<>();
|
||||||
|
content.add(Text.literal("first line, text version"));
|
||||||
|
content.add(Text.literal("Second line"));
|
||||||
|
content.add(null);
|
||||||
|
content.add(Text.literal("second page"));
|
||||||
|
content.add(Text.literal("dqdq"));
|
||||||
|
content.add(Text.literal("dqdqd"));
|
||||||
|
content.add(Text.literal("dqdqd"));
|
||||||
|
content.add(Text.literal("dqdq"));
|
||||||
|
content.add(Text.literal("dqdq"));
|
||||||
|
content.add(Text.literal("dqdq"));
|
||||||
|
content.add(Text.literal("dqdqd"));
|
||||||
|
content.add(Text.literal("third page"));
|
||||||
|
content.add(Text.literal("dqdqd"));
|
||||||
|
content.add(Text.literal("dqdqd"));
|
||||||
|
api.createTable(context.getSource().getPlayer(), content, Text.literal("My Text Header"), true);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int selectPageCommand(CommandContext<ServerCommandSource> context) throws CommandSyntaxException {
|
||||||
|
try {
|
||||||
|
int page = IntegerArgumentType.getInteger(context, "page");
|
||||||
|
api.display(context.getSource().getPlayerOrThrow(), page);
|
||||||
|
} catch(PageIndexOutOfBoundException e) {
|
||||||
|
throw new CommandSyntaxException(new SimpleCommandExceptionType(e.getText()), e.getText());
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TestType {
|
||||||
|
String,
|
||||||
|
Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package fr.altarik.toolbox.pagination.precondition;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This predicate returns true if the String is not null or
|
||||||
|
* if its content is not blank (empty or only contains whitespaces)
|
||||||
|
*/
|
||||||
|
public class ContentCondition implements Predicate<String> {
|
||||||
|
@Override
|
||||||
|
public boolean test(String s) {
|
||||||
|
return s != null && !s.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package fr.altarik.toolbox.pagination.precondition;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This predicate returns true if its length doesn't exceed 50 characters.
|
||||||
|
*/
|
||||||
|
public class HeaderCondition implements Predicate<String> {
|
||||||
|
@Override
|
||||||
|
public boolean test(String header) {
|
||||||
|
return header.length() <= 50;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package fr.altarik.toolbox.pagination.precondition;
|
||||||
|
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This predicate returns true if the player isn't null, false otherwise
|
||||||
|
*/
|
||||||
|
public class NullPlayerCondition implements Predicate<ServerPlayerEntity> {
|
||||||
|
@Override
|
||||||
|
public boolean test(ServerPlayerEntity player) {
|
||||||
|
return player != null;
|
||||||
|
}
|
||||||
|
}
|
14
Pagination/src/main/resources/Pagination.mixins.json
Normal file
14
Pagination/src/main/resources/Pagination.mixins.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "fr.altarik.toolbox.task.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_17",
|
||||||
|
"mixins": [
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
],
|
||||||
|
"verbose": false,
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Pagination/src/main/resources/assets/pagination/icon.png
Normal file
BIN
Pagination/src/main/resources/assets/pagination/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 323 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"api.pagination.page_lower_than_0": "argument page is lower than 0",
|
||||||
|
"api.pagination.page_higher_than_expected": "There's %d paginated pages but you wanted page n°%d"
|
||||||
|
}
|
41
Pagination/src/main/resources/fabric.mod.json
Normal file
41
Pagination/src/main/resources/fabric.mod.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "toolbox-pagination",
|
||||||
|
"version": "${version}",
|
||||||
|
"name": "Altarik Toolbox Pagination",
|
||||||
|
"description": "A mod to use to paginate long result to player in chat",
|
||||||
|
"authors": [
|
||||||
|
"Altarik"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"contact": {
|
||||||
|
"homepage": "https://altarik.fr"
|
||||||
|
},
|
||||||
|
"license": "Altarik @ All-Rights-Reserved ",
|
||||||
|
"icon": "assets/pagination/icon.png",
|
||||||
|
"environment": "*",
|
||||||
|
"entrypoints": {
|
||||||
|
"main": [
|
||||||
|
"fr.altarik.toolbox.pagination.Pagination"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mixins": [
|
||||||
|
"Pagination.mixins.json"
|
||||||
|
],
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": "^${loaderVersion}",
|
||||||
|
"fabric-api": "*",
|
||||||
|
"minecraft": "${minecraftVersion}",
|
||||||
|
"java": ">=17"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"modmenu": {
|
||||||
|
"badges": [ "library" ],
|
||||||
|
"parent": {
|
||||||
|
"parent": "toolbox"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'java'
|
|
||||||
id 'maven-publish'
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit_version}"
|
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.junit_version}"
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
withSourcesJar()
|
|
||||||
withJavadocJar()
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
|
@ -0,0 +1,18 @@
|
|||||||
|
package fr.altarik.toolbox.task;
|
||||||
|
|
||||||
|
public abstract class AltarikRunnable implements Runnable {
|
||||||
|
|
||||||
|
private boolean isCancelled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Warning: Some task cannot be cancelled (mostly async tasks like {@link fr.altarik.toolbox.task.async.AsyncTasks})
|
||||||
|
* The result of this call is ignored in this case, you can still add a way to not execute its content (like if(isCancelled) return;)
|
||||||
|
*/
|
||||||
|
public void cancel() {
|
||||||
|
this.isCancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return isCancelled;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package fr.altarik.toolbox.task;
|
||||||
|
|
||||||
|
public interface PeriodicTaskI extends TaskI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a task periodically
|
||||||
|
* @param function the function to execute
|
||||||
|
* @param delay delay before starting the task
|
||||||
|
* @param period time to wait between runs
|
||||||
|
* @throws InterruptedException When executed asynchronously, task may be interrupted
|
||||||
|
* @see fr.altarik.toolbox.task.sync.PeriodicSyncTask
|
||||||
|
*/
|
||||||
|
void addTask(AltarikRunnable function, long delay, long period) throws InterruptedException;
|
||||||
|
}
|
41
Tasks/src/main/java/fr/altarik/toolbox/task/Scheduler.java
Normal file
41
Tasks/src/main/java/fr/altarik/toolbox/task/Scheduler.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package fr.altarik.toolbox.task;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
public class Scheduler implements Runnable {
|
||||||
|
|
||||||
|
private final Queue<SchedulerTaskData> tasks;
|
||||||
|
private final SendTaskWorkerI worker;
|
||||||
|
|
||||||
|
public Scheduler(SendTaskWorkerI worker, Queue<SchedulerTaskData> tasks) {
|
||||||
|
this.worker = worker;
|
||||||
|
this.tasks = tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
List<SchedulerTaskData> removeList = new ArrayList<>(tasks.size());
|
||||||
|
for(SchedulerTaskData data : tasks) {
|
||||||
|
if(!data.getFunction().isCancelled()) {
|
||||||
|
long currentDelay = data.getCurrentDelay();
|
||||||
|
if(currentDelay != 0) {
|
||||||
|
data.setCurrentDelay(currentDelay - 1);
|
||||||
|
} else {
|
||||||
|
worker.sendTask(data.getFunction());
|
||||||
|
if(data.getPeriod() == -1) {
|
||||||
|
removeList.add(data);
|
||||||
|
} else {
|
||||||
|
data.setCurrentDelay(data.getPeriod());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
removeList.add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(SchedulerTaskData toRemove : removeList) {
|
||||||
|
tasks.remove(toRemove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package fr.altarik.toolbox.task;
|
||||||
|
|
||||||
|
public class SchedulerTaskData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delay before executing the function for the first time
|
||||||
|
* Correspond to tick in synchronous context and milliseconds in asynchronous context
|
||||||
|
*/
|
||||||
|
private final long delay;
|
||||||
|
/**
|
||||||
|
* Period of time before re-executing the function
|
||||||
|
* Correspond to tick in synchronous context and milliseconds in asynchronous context
|
||||||
|
*/
|
||||||
|
private final long period;
|
||||||
|
private final AltarikRunnable function;
|
||||||
|
|
||||||
|
private long currentDelay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Delay and Period times corresponds to tick in synchronous context and milliseconds in asynchronous context
|
||||||
|
*
|
||||||
|
* @param function instructions to execute
|
||||||
|
* @param delay Delay before executing the function for the first time
|
||||||
|
* @param period Period of time before re-executing the function
|
||||||
|
*/
|
||||||
|
public SchedulerTaskData(AltarikRunnable function, long delay, long period) {
|
||||||
|
this.function = function;
|
||||||
|
this.delay = delay;
|
||||||
|
this.period = period;
|
||||||
|
this.currentDelay = delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AltarikRunnable getFunction() {
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCurrentDelay() {
|
||||||
|
return currentDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentDelay(long currentDelay) {
|
||||||
|
this.currentDelay = currentDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDelay() {
|
||||||
|
return delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPeriod() {
|
||||||
|
return period;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package fr.altarik.toolbox.task;
|
||||||
|
|
||||||
|
public interface SendTaskWorkerI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal use for scheduler, do not use.
|
||||||
|
* Scheduler use this method to send the task to execute to worker
|
||||||
|
* @param task task to execute now
|
||||||
|
*/
|
||||||
|
void sendTask(AltarikRunnable task);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package fr.altarik.toolbox.task;
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
|
||||||
|
public class ServerTickListener {
|
||||||
|
|
||||||
|
private final Runnable task;
|
||||||
|
|
||||||
|
public ServerTickListener(Runnable syncTask) {
|
||||||
|
this.task = syncTask;
|
||||||
|
ServerTickEvents.START_SERVER_TICK.register(this::onServerTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onServerTick(MinecraftServer minecraftServer) {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
12
Tasks/src/main/java/fr/altarik/toolbox/task/TaskI.java
Normal file
12
Tasks/src/main/java/fr/altarik/toolbox/task/TaskI.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package fr.altarik.toolbox.task;
|
||||||
|
|
||||||
|
public interface TaskI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send task to worker, execution depends on implementation
|
||||||
|
* @param function task you send to worker
|
||||||
|
* @throws InterruptedException used by asynchronous workers if threads has been interrupted or shutdown
|
||||||
|
*/
|
||||||
|
void addTask(AltarikRunnable function) throws InterruptedException;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package fr.altarik.toolbox.task.async;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.task.*;
|
||||||
|
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A task manager to execute periodic tasks asynchronously. A scheduler on the main/server thread will send the task to
|
||||||
|
* worker threads.
|
||||||
|
*/
|
||||||
|
public class AsyncPeriodicTasks implements PeriodicTaskI, AsyncTaskI, SendTaskWorkerI {
|
||||||
|
|
||||||
|
private final ExecutorService worker;
|
||||||
|
private final Queue<SchedulerTaskData> tasks;
|
||||||
|
protected final Scheduler scheduler;
|
||||||
|
private final ServerTickListener listener;
|
||||||
|
|
||||||
|
private AsyncPeriodicTasks(int numberOfWorker) {
|
||||||
|
if(numberOfWorker == 1) {
|
||||||
|
worker = Executors.newSingleThreadExecutor();
|
||||||
|
} else if (numberOfWorker <= 0) {
|
||||||
|
worker = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||||
|
} else {
|
||||||
|
worker = Executors.newFixedThreadPool(numberOfWorker);
|
||||||
|
}
|
||||||
|
tasks = new ConcurrentLinkedQueue<>();
|
||||||
|
this.scheduler = new Scheduler(this, tasks);
|
||||||
|
this.listener = new ServerTickListener(scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method at startup or before first use of {@link AsyncTasks#addTask(AltarikRunnable)}, cause without it, nothing will work
|
||||||
|
* This method declare worker thread and start it, without call it, by calling addTask(Runnable), it'll add your task to Queue, but tasks will never be consumed.
|
||||||
|
*
|
||||||
|
* @return an instance of AsyncTasks
|
||||||
|
*/
|
||||||
|
public static PeriodicTaskI initialize(int numberOfWorker) {
|
||||||
|
return new AsyncPeriodicTasks(numberOfWorker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PeriodicTaskI initialize() {
|
||||||
|
return initialize(Runtime.getRuntime().availableProcessors());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the task to the scheduler, the task is executed at the next server tick and at every following tick
|
||||||
|
* @param function the function which will be executed
|
||||||
|
* @throws InterruptedException if worker has terminated or is shutting down
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addTask(AltarikRunnable function) throws InterruptedException {
|
||||||
|
this.addTask(function, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the task to the scheduler, executed depending on the parameters (delay and period)
|
||||||
|
* @param function the function to execute
|
||||||
|
* @param delay delay in tick before starting the task
|
||||||
|
* @param period time in tick to wait between runs
|
||||||
|
* @throws InterruptedException if worker has terminated or is shutting down
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addTask(AltarikRunnable function, long delay, long period) throws InterruptedException {
|
||||||
|
if(worker.isTerminated() || worker.isShutdown()) {
|
||||||
|
throw new InterruptedException("Worker has been terminated or shutdown, it's impossible to add new task");
|
||||||
|
}
|
||||||
|
tasks.add(new SchedulerTaskData(function, delay, period - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to execute task you already send in 10 seconds, otherwise workers are killed.
|
||||||
|
* @throws AsyncTasks.UnfinishedTasksException if workers has been shutdown before finishing every tasks
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
worker.shutdown();
|
||||||
|
boolean result = worker.awaitTermination(10, TimeUnit.SECONDS);
|
||||||
|
if(!result) {
|
||||||
|
worker.shutdownNow();
|
||||||
|
throw new AsyncTasks.UnfinishedTasksException("Tasks take too many time to finish, shutdown has been enforce");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendTask(AltarikRunnable task) {
|
||||||
|
worker.submit(task);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package fr.altarik.toolbox.task.async;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.task.TaskI;
|
||||||
|
|
||||||
|
public interface AsyncTaskI extends TaskI, AutoCloseable {
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
package fr.altarik.toolbox.asynctasks;
|
package fr.altarik.toolbox.task.async;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.task.AltarikRunnable;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@ -7,7 +9,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
/**
|
/**
|
||||||
* A non-blocking small sized time-consuming tasks to executed asynchronously.
|
* A non-blocking small sized time-consuming tasks to executed asynchronously.
|
||||||
*/
|
*/
|
||||||
public class AsyncTasks implements AutoCloseable {
|
public class AsyncTasks implements AsyncTaskI {
|
||||||
|
|
||||||
private final ExecutorService worker;
|
private final ExecutorService worker;
|
||||||
|
|
||||||
@ -15,23 +17,23 @@ public class AsyncTasks implements AutoCloseable {
|
|||||||
if(numberOfWorker == 1) {
|
if(numberOfWorker == 1) {
|
||||||
worker = Executors.newSingleThreadExecutor();
|
worker = Executors.newSingleThreadExecutor();
|
||||||
} else if (numberOfWorker <= 0) {
|
} else if (numberOfWorker <= 0) {
|
||||||
worker = Executors.newCachedThreadPool();
|
worker = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||||
} else {
|
} else {
|
||||||
worker = Executors.newFixedThreadPool(numberOfWorker);
|
worker = Executors.newFixedThreadPool(numberOfWorker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this method at startup or before first use of {@link AsyncTasks#addTask(Runnable)}, cause without it, nothing will work
|
* Call this method at startup or before first use of {@link AsyncTasks#addTask(AltarikRunnable)}, cause without it, nothing will work
|
||||||
* This method declare worker thread and start it, without call it, by calling addTask(Runnable), it'll add your task to Queue, but tasks will never be consumed.
|
* This method declare worker thread and start it, without call it, by calling addTask(Runnable), it'll add your task to Queue, but tasks will never be consumed.
|
||||||
*
|
*
|
||||||
* @return an instance of AsyncTasks
|
* @return an instance of AsyncTasks
|
||||||
*/
|
*/
|
||||||
public static AsyncTasks initialize(int numberOfWorker) {
|
public static AsyncTaskI initialize(int numberOfWorker) {
|
||||||
return new AsyncTasks(numberOfWorker);
|
return new AsyncTasks(numberOfWorker);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AsyncTasks initialize() {
|
public static AsyncTaskI initialize() {
|
||||||
return initialize(Runtime.getRuntime().availableProcessors());
|
return initialize(Runtime.getRuntime().availableProcessors());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +67,7 @@ public class AsyncTasks implements AutoCloseable {
|
|||||||
* @param function task to be executed
|
* @param function task to be executed
|
||||||
* @throws InterruptedException when worker thread or BlockQueue has been interrupted while waiting (which is anormal)
|
* @throws InterruptedException when worker thread or BlockQueue has been interrupted while waiting (which is anormal)
|
||||||
*/
|
*/
|
||||||
public void addTask(Runnable function) throws InterruptedException {
|
public void addTask(AltarikRunnable function) throws InterruptedException {
|
||||||
if(worker.isTerminated() || worker.isShutdown()) {
|
if(worker.isTerminated() || worker.isShutdown()) {
|
||||||
throw new InterruptedException("Worker has been terminated or shutdown, it's impossible to add new task");
|
throw new InterruptedException("Worker has been terminated or shutdown, it's impossible to add new task");
|
||||||
}
|
}
|
||||||
@ -75,9 +77,11 @@ public class AsyncTasks implements AutoCloseable {
|
|||||||
/**
|
/**
|
||||||
* This method is call when you want to close workers and wait for waiting tasks to finish
|
* This method is call when you want to close workers and wait for waiting tasks to finish
|
||||||
*
|
*
|
||||||
|
* @throws UnfinishedTasksException when all tasks cannot be terminated in 10 seconds
|
||||||
|
* @throws InterruptedException if interrupted while waiting for tasks to finish
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void close() throws Exception {
|
public void close() throws UnfinishedTasksException, InterruptedException {
|
||||||
worker.shutdown();
|
worker.shutdown();
|
||||||
boolean result = worker.awaitTermination(10, TimeUnit.SECONDS);
|
boolean result = worker.awaitTermination(10, TimeUnit.SECONDS);
|
||||||
if(!result) {
|
if(!result) {
|
@ -0,0 +1,58 @@
|
|||||||
|
package fr.altarik.toolbox.task.server;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.task.PeriodicTaskI;
|
||||||
|
import fr.altarik.toolbox.task.TaskI;
|
||||||
|
import fr.altarik.toolbox.task.async.AsyncPeriodicTasks;
|
||||||
|
import fr.altarik.toolbox.task.async.AsyncTaskI;
|
||||||
|
import fr.altarik.toolbox.task.async.AsyncTasks;
|
||||||
|
import fr.altarik.toolbox.task.sync.PeriodicSyncTask;
|
||||||
|
import net.fabricmc.api.DedicatedServerModInitializer;
|
||||||
|
|
||||||
|
public class DedicatedServerTask implements DedicatedServerModInitializer {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final TaskI asyncWorkers = AsyncTasks.initialize();
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final PeriodicTaskI periodicSyncTask = PeriodicSyncTask.initialize();
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final AsyncTaskI asyncTasks = AsyncTasks.initialize();
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final PeriodicTaskI periodicAsyncTask = AsyncPeriodicTasks.initialize();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeServer() {
|
||||||
|
/* try {
|
||||||
|
asyncWorkers.addTask(new AltarikRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
System.out.println("Hello world 1");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
periodicSyncTask.addTask(new AltarikRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
System.out.println("Hello world 2");
|
||||||
|
}
|
||||||
|
}, 40, 60);
|
||||||
|
asyncTasks.addTask(new AltarikRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
System.out.println("Hello world 3 : " + Thread.currentThread().getName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
periodicAsyncTask.addTask(new AltarikRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
System.out.println("Hello world 4 : " + Thread.currentThread().getName());
|
||||||
|
}
|
||||||
|
}, 60, 80);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public TaskI getAsyncWorkers() {
|
||||||
|
return asyncWorkers;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package fr.altarik.toolbox.task.sync;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.task.*;
|
||||||
|
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
|
public class PeriodicSyncTask implements PeriodicTaskI, SendTaskWorkerI {
|
||||||
|
|
||||||
|
private final ServerTickListener listener;
|
||||||
|
private final Queue<SchedulerTaskData> tasks;
|
||||||
|
protected final Scheduler scheduler;
|
||||||
|
|
||||||
|
private PeriodicSyncTask() {
|
||||||
|
this.tasks = new ConcurrentLinkedQueue<>();
|
||||||
|
this.scheduler = new Scheduler(this, tasks);
|
||||||
|
this.listener = new ServerTickListener(scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static PeriodicTaskI initialize() {
|
||||||
|
return new PeriodicSyncTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTask(AltarikRunnable function) {
|
||||||
|
addTask(function, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTask(AltarikRunnable function, long delay, long period) {
|
||||||
|
tasks.add(new SchedulerTaskData(function, delay, period - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendTask(AltarikRunnable task) {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package fr.altarik.toolbox.task.sync;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.task.*;
|
||||||
|
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
|
public class SyncTask implements TaskI, SendTaskWorkerI {
|
||||||
|
|
||||||
|
private final ServerTickListener listener;
|
||||||
|
private final Queue<SchedulerTaskData> tasks;
|
||||||
|
protected final Scheduler scheduler;
|
||||||
|
|
||||||
|
private SyncTask() {
|
||||||
|
this.tasks = new ConcurrentLinkedQueue<>();
|
||||||
|
this.scheduler = new Scheduler(this, tasks);
|
||||||
|
this.listener = new ServerTickListener(scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TaskI initialize() {
|
||||||
|
return new SyncTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addTask(AltarikRunnable function) {
|
||||||
|
addTask(function, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTask(AltarikRunnable function, int delay) {
|
||||||
|
tasks.add(new SchedulerTaskData(function, delay, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendTask(AltarikRunnable task) {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
}
|
15
Tasks/src/main/resources/Task.mixins.json
Normal file
15
Tasks/src/main/resources/Task.mixins.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"package": "fr.altarik.toolbox.task.mixin",
|
||||||
|
"compatibilityLevel": "JAVA_17",
|
||||||
|
"mixins": [
|
||||||
|
],
|
||||||
|
"client": [
|
||||||
|
],
|
||||||
|
"verbose": false,
|
||||||
|
"injectors": {
|
||||||
|
"defaultRequire": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
Tasks/src/main/resources/assets/tasks/icon.png
Normal file
BIN
Tasks/src/main/resources/assets/tasks/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 323 KiB |
34
Tasks/src/main/resources/fabric.mod.json
Normal file
34
Tasks/src/main/resources/fabric.mod.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "toolbox-task",
|
||||||
|
"version": "${version}",
|
||||||
|
"name": "Altarik Toolbox Task",
|
||||||
|
"description": "A mod to use as a dependency for others to schedule tasks",
|
||||||
|
"authors": [
|
||||||
|
"Altarik"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"contact": {
|
||||||
|
"homepage": "https://altarik.fr"
|
||||||
|
},
|
||||||
|
"license": "Altarik @ All-Rights-Reserved ",
|
||||||
|
"icon": "assets/tasks/icon.png",
|
||||||
|
"environment": "*",
|
||||||
|
"entrypoints": {
|
||||||
|
"server": [
|
||||||
|
"fr.altarik.toolbox.task.server.DedicatedServerTask"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mixins": [
|
||||||
|
"Task.mixins.json"
|
||||||
|
],
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": "^${loaderVersion}",
|
||||||
|
"fabric-api": "*",
|
||||||
|
"minecraft": "${minecraftVersion}",
|
||||||
|
"java": ">=17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
package fr.altarik.toolbox;
|
package fr.altarik.toolbox.task.async;
|
||||||
|
|
||||||
import fr.altarik.toolbox.asynctasks.AsyncTasks;
|
import fr.altarik.toolbox.task.AltarikRunnable;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -18,15 +18,18 @@ class AsyncTaskTest {
|
|||||||
@Test
|
@Test
|
||||||
void testAsyncOp() throws Exception {
|
void testAsyncOp() throws Exception {
|
||||||
int numberOfTasks = 10000;
|
int numberOfTasks = 10000;
|
||||||
System.out.println("Initializing async tasks worker");
|
// System.out.println("Initializing async tasks worker");
|
||||||
AsyncTasks worker = AsyncTasks.initialize(1); // only testing on a single worker, otherwise result have a high chance to not be in the order we want
|
AsyncTaskI worker = AsyncTasks.initialize(1); // only testing on a single worker, otherwise result have a high chance to not be in the order we want
|
||||||
Stack<Integer> results = new Stack<>();
|
Stack<Integer> results = new Stack<>();
|
||||||
for(int i = 0; i < numberOfTasks; i++) {
|
for(int i = 0; i < numberOfTasks; i++) {
|
||||||
System.out.println(log("sending task " + i));
|
// System.out.println(log("sending task " + i));
|
||||||
AtomicInteger atomicInteger = new AtomicInteger(i);
|
AtomicInteger atomicInteger = new AtomicInteger(i);
|
||||||
worker.addTask(() -> {
|
worker.addTask(new AltarikRunnable() {
|
||||||
System.out.println(log(" task " + atomicInteger.get()));
|
@Override
|
||||||
results.push(atomicInteger.get());
|
public void run() {
|
||||||
|
// System.out.println(log(" task " + atomicInteger.get()));
|
||||||
|
results.push(atomicInteger.get());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
worker.close(); // wait until all worker terminated
|
worker.close(); // wait until all worker terminated
|
||||||
@ -35,6 +38,5 @@ class AsyncTaskTest {
|
|||||||
expected[i] = i;
|
expected[i] = i;
|
||||||
}
|
}
|
||||||
assertArrayEquals(expected, results.toArray());
|
assertArrayEquals(expected, results.toArray());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package fr.altarik.toolbox.task.async;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.task.AltarikRunnable;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Stack;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class PeriodicAsyncTaskTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPeriodicASyncTask() throws Exception {
|
||||||
|
AsyncPeriodicTasks worker = (AsyncPeriodicTasks) AsyncPeriodicTasks.initialize(1);
|
||||||
|
Stack<AtomicInteger> results = new Stack<>();
|
||||||
|
AtomicInteger value1 = new AtomicInteger(1);
|
||||||
|
AtomicInteger value2 = new AtomicInteger(2);
|
||||||
|
AltarikRunnable runnable1 = new AltarikRunnable() {
|
||||||
|
private int i = 0;
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
results.add(value1);
|
||||||
|
i++;
|
||||||
|
if(i == 2)
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AltarikRunnable runnable2 = new AltarikRunnable() {
|
||||||
|
private int i = 0;
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
results.add(value2);
|
||||||
|
i++;
|
||||||
|
if(i == 4)
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
worker.addTask(runnable1, 1, 4);
|
||||||
|
worker.addTask(runnable2, 0, 2);
|
||||||
|
for(int i = 0; i < 10; i++) {
|
||||||
|
worker.scheduler.run();
|
||||||
|
}
|
||||||
|
AtomicInteger[] expected = {
|
||||||
|
value2,
|
||||||
|
value1,
|
||||||
|
value2,
|
||||||
|
value2,
|
||||||
|
value1,
|
||||||
|
value2,
|
||||||
|
value2,
|
||||||
|
value1
|
||||||
|
};
|
||||||
|
worker.close();
|
||||||
|
Assertions.assertArrayEquals(expected, results.toArray());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package fr.altarik.toolbox.task.sync;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.task.AltarikRunnable;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
class PeriodicSyncTaskTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPeriodicSyncTask() {
|
||||||
|
List<AtomicInteger> results = new ArrayList<>();
|
||||||
|
PeriodicSyncTask worker = (PeriodicSyncTask) PeriodicSyncTask.initialize();
|
||||||
|
AtomicInteger value1 = new AtomicInteger(1);
|
||||||
|
AtomicInteger value2 = new AtomicInteger(2);
|
||||||
|
worker.addTask(new AltarikRunnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
results.add(value1);
|
||||||
|
}
|
||||||
|
}, 1, 3);
|
||||||
|
worker.addTask(new AltarikRunnable() {
|
||||||
|
private int i = 0;
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
results.add(value2);
|
||||||
|
if(i++ == 5)
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for(int i = 0; i < 10; i++) {
|
||||||
|
worker.scheduler.run();
|
||||||
|
}
|
||||||
|
AtomicInteger[] expected = {
|
||||||
|
value2,
|
||||||
|
value1,
|
||||||
|
value2,
|
||||||
|
value2,
|
||||||
|
value2,
|
||||||
|
value1,
|
||||||
|
value2,
|
||||||
|
value2,
|
||||||
|
value1
|
||||||
|
};
|
||||||
|
Assertions.assertArrayEquals(expected, results.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package fr.altarik.toolbox.task.sync;
|
||||||
|
|
||||||
|
import fr.altarik.toolbox.task.AltarikRunnable;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
class SyncTaskTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOneTimeTask() {
|
||||||
|
SyncTask worker = (SyncTask) SyncTask.initialize();
|
||||||
|
List<AtomicInteger> results = new ArrayList<>();
|
||||||
|
AtomicInteger value1 = new AtomicInteger(1);
|
||||||
|
AtomicInteger value2 = new AtomicInteger(2);
|
||||||
|
AltarikRunnable task1 = new AltarikRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
results.add(value1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AltarikRunnable task2 = new AltarikRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
results.add(value2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
worker.addTask(task1);
|
||||||
|
worker.scheduler.run();
|
||||||
|
worker.scheduler.run();
|
||||||
|
worker.addTask(task2);
|
||||||
|
worker.addTask(task1);
|
||||||
|
worker.addTask(task2);
|
||||||
|
worker.scheduler.run();
|
||||||
|
worker.addTask(task1);
|
||||||
|
worker.scheduler.run();
|
||||||
|
AtomicInteger[] expected = {
|
||||||
|
value1,
|
||||||
|
value2,
|
||||||
|
value1,
|
||||||
|
value2,
|
||||||
|
value1
|
||||||
|
};
|
||||||
|
Assertions.assertArrayEquals(expected, results.toArray());
|
||||||
|
}
|
||||||
|
}
|
149
build.gradle
149
build.gradle
@ -1,31 +1,35 @@
|
|||||||
allprojects {
|
import fr.altarik.CreateTag
|
||||||
group = project.maven_group
|
import fr.altarik.ReportDiscord
|
||||||
version = project.maven_version
|
|
||||||
|
plugins {
|
||||||
|
id 'fabric-loom' version '1.6-SNAPSHOT' apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
Properties local = new Properties()
|
||||||
apply plugin: 'java'
|
try {
|
||||||
apply plugin: 'maven-publish'
|
local.load(new FileInputStream(rootProject.file("local.properties")))
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
} catch (IOException ignored) {}
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
|
|
||||||
publishing {
|
group = project.maven_group
|
||||||
publications {
|
version = project.maven_version
|
||||||
mavenJava(MavenPublication) {
|
|
||||||
from components.java
|
String webhookId = getEnv("DISCORD_PUB_ID", local.getProperty("discord_pub_id"))
|
||||||
}
|
String webhookToken = getEnv("DISCORD_PUB_TOKEN", local.getProperty("discord_pub_token"))
|
||||||
}
|
String repoUrl = "https://repo.altarik.fr/#/" + (project.version.endsWith('SNAPSHOT') ? 'snapshots/' : 'releases/') + project.group.replace(".", "/") + "/" + project.rootProject.name + "/" + project.version
|
||||||
repositories {
|
|
||||||
maven {
|
var reportConfig = new ReportDiscord.ReportData("https://discord.com/api/", webhookId, webhookToken, project.rootProject.name, "Update " + project.version + " has been published", repoUrl)
|
||||||
name 'altarik'
|
|
||||||
url 'https://repo.altarik.fr/'.concat(project.version.endsWith('SNAPSHOT') ? 'snapshots/' : 'releases/')
|
String giteaToken = getEnv("GITEA_TOKEN", local.getProperty("gitea_token"))
|
||||||
credentials {
|
|
||||||
username = project.repo_username
|
var releaseConfig = new CreateTag.CreateReleaseData("https://git.altarik.fr", project.git_owner, project.git_repo, "v" + project.version as String, giteaToken)
|
||||||
password = project.repo_password
|
|
||||||
}
|
|
||||||
}
|
allprojects {
|
||||||
}
|
apply plugin: 'maven-publish'
|
||||||
}
|
apply plugin: 'fabric-loom'
|
||||||
|
|
||||||
|
group = project.maven_group
|
||||||
|
version = project.maven_version
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
@ -36,7 +40,100 @@ subprojects {
|
|||||||
name 'altarik-releases'
|
name 'altarik-releases'
|
||||||
url 'https://repo.altarik.fr/releases/'
|
url 'https://repo.altarik.fr/releases/'
|
||||||
}
|
}
|
||||||
|
maven {
|
||||||
|
name = 'Fabric'
|
||||||
|
url = 'https://maven.fabricmc.net/'
|
||||||
|
}
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
mavenJava(MavenPublication) {
|
||||||
|
from components.java
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name 'altarik'
|
||||||
|
url 'https://repo.altarik.fr/'.concat(project.version.endsWith('SNAPSHOT') ? 'snapshots/' : 'releases/')
|
||||||
|
credentials {
|
||||||
|
username = getEnv("REPO_USERNAME", local.getProperty("repo_username"))
|
||||||
|
password = getEnv("REPO_PASSWORD", local.getProperty("repo_password"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processResources {
|
||||||
|
inputs.property "version", project.version
|
||||||
|
inputs.property "minecraftVersion", project.minecraft_version
|
||||||
|
inputs.property "loaderVersion", project.loader_version
|
||||||
|
|
||||||
|
filesMatching("fabric.mod.json") {
|
||||||
|
expand inputs.properties
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile).configureEach {
|
||||||
|
it.options.encoding = "UTF-8"
|
||||||
|
it.options.release = 17
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit_version}"
|
||||||
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.junit_version}"
|
||||||
|
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||||
|
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
|
||||||
|
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||||
|
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
withSourcesJar()
|
||||||
|
withJavadocJar()
|
||||||
|
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(path: ":Core", configuration: "namedElements")
|
||||||
|
implementation project(path: ":Database", configuration: "namedElements")
|
||||||
|
implementation project(path: ":Pagination", configuration: "namedElements")
|
||||||
|
implementation project(path: ":Tasks", configuration: "namedElements")
|
||||||
|
include subprojects.collect { project -> project }
|
||||||
|
}
|
||||||
|
|
||||||
|
static def getEnv(String envName, String defaultValue) {
|
||||||
|
String r = System.getenv(envName)
|
||||||
|
if(r != null) {
|
||||||
|
return r
|
||||||
|
} else {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("reportToDiscord", ReportDiscord) {
|
||||||
|
config.set(reportConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("createTag", CreateTag) {
|
||||||
|
config.set(releaseConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*jar {
|
||||||
|
dependsOn subprojects.jar
|
||||||
|
subprojects.each { project ->
|
||||||
|
from(project.jar) {
|
||||||
|
into("META-INF/jars/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
11
buildSrc/build.gradle
Normal file
11
buildSrc/build.gradle
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "com.squareup.okhttp3:okhttp:${project.okhttp_version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
1
buildSrc/gradle.properties
Normal file
1
buildSrc/gradle.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
okhttp_version=4.12.0
|
7
buildSrc/settings.gradle
Normal file
7
buildSrc/settings.gradle
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rootProject.name = "buildSrc"
|
48
buildSrc/src/main/java/fr/altarik/CreateTag.java
Normal file
48
buildSrc/src/main/java/fr/altarik/CreateTag.java
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package fr.altarik;
|
||||||
|
|
||||||
|
import okhttp3.*;
|
||||||
|
import org.gradle.api.DefaultTask;
|
||||||
|
import org.gradle.api.GradleException;
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
|
import org.gradle.api.tasks.Input;
|
||||||
|
import org.gradle.api.tasks.TaskAction;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public abstract class CreateTag extends DefaultTask {
|
||||||
|
|
||||||
|
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||||
|
private final OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public abstract Property<CreateReleaseData> getConfig();
|
||||||
|
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
public void create() throws IOException {
|
||||||
|
CreateReleaseData data = getConfig().get();
|
||||||
|
String postUrl = data.baseUrl() + "/api/v1/repos/" + data.owner() + "/" + data.repoName() + "/tags";
|
||||||
|
RequestBody body = RequestBody.create("""
|
||||||
|
{
|
||||||
|
"tag_name": \"""" + data.tagName() + "\"" + """
|
||||||
|
}
|
||||||
|
""", JSON);
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(postUrl)
|
||||||
|
.post(body)
|
||||||
|
.header("Authorization", "token " + data.giteaToken())
|
||||||
|
.build();
|
||||||
|
try(Response response = client.newCall(request).execute()) {
|
||||||
|
if(!response.isSuccessful()) {
|
||||||
|
if(response.code() != 409)
|
||||||
|
throw new GradleException("Cannot create tag, server answer with code " + response.code() + " and message : " + response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public record CreateReleaseData(String baseUrl, String owner, String repoName, String tagName, String giteaToken) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
56
buildSrc/src/main/java/fr/altarik/ReportDiscord.java
Normal file
56
buildSrc/src/main/java/fr/altarik/ReportDiscord.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package fr.altarik;
|
||||||
|
|
||||||
|
import okhttp3.*;
|
||||||
|
import org.gradle.api.DefaultTask;
|
||||||
|
import org.gradle.api.GradleException;
|
||||||
|
import org.gradle.api.provider.Property;
|
||||||
|
import org.gradle.api.tasks.Input;
|
||||||
|
import org.gradle.api.tasks.TaskAction;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public abstract class ReportDiscord extends DefaultTask {
|
||||||
|
|
||||||
|
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public abstract Property<ReportData> getConfig();
|
||||||
|
|
||||||
|
private final OkHttpClient client = new OkHttpClient();
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
public void report() throws IOException {
|
||||||
|
ReportData data = getConfig().get();
|
||||||
|
String message = data.message();
|
||||||
|
RequestBody body = RequestBody.create("""
|
||||||
|
{
|
||||||
|
"embeds": [
|
||||||
|
{
|
||||||
|
"title": "A new update of\s""" + data.projectName() + """
|
||||||
|
is available",
|
||||||
|
"description":\s""" + "\"" + message + "\"" + """
|
||||||
|
,
|
||||||
|
"url":\s""" + "\"" + data.url() + "\"" + """
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""", JSON);
|
||||||
|
|
||||||
|
String url = data.baseUrl() + "/webhooks/" + data.webhookId + "/" + data.webhookToken;
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.post(body)
|
||||||
|
.build();
|
||||||
|
try(Response response = client.newCall(request).execute()) {
|
||||||
|
getLogger().info("report sent");
|
||||||
|
if(!response.isSuccessful()) {
|
||||||
|
throw new GradleException("Discord returned a " + response.code() + " code: " + response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ReportData(String baseUrl, String webhookId, String webhookToken, String projectName, String message, String url) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,14 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1G
|
||||||
|
fabric.loom.multiProjectOptimisation=true
|
||||||
|
|
||||||
junit_version=5.9.0
|
junit_version=5.9.0
|
||||||
|
minecraft_version=1.20.4
|
||||||
|
yarn_mappings=1.20.4+build.3
|
||||||
|
loader_version=0.15.6
|
||||||
|
fabric_version=0.97.1+1.20.4
|
||||||
|
|
||||||
maven_group=fr.altarik.toolbox
|
maven_group=fr.altarik.toolbox
|
||||||
maven_version=2.0.0-SNAPSHOT
|
maven_version=5.1.0
|
||||||
repo_username=Altarik
|
|
||||||
repo_password=password
|
git_owner=quentinlegot
|
||||||
|
git_repo=Toolbox
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -1,2 +1,16 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
name = 'Fabric'
|
||||||
|
url = 'https://maven.fabricmc.net/'
|
||||||
|
}
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rootProject.name = 'Toolbox'
|
rootProject.name = 'Toolbox'
|
||||||
include(':Tasks', ':Database')
|
include(':Tasks')
|
||||||
|
include(':Database')
|
||||||
|
include(':Pagination')
|
||||||
|
include(':Core')
|
||||||
|
BIN
src/main/resources/assets/toolbox/icon.png
Normal file
BIN
src/main/resources/assets/toolbox/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 323 KiB |
35
src/main/resources/fabric.mod.json
Normal file
35
src/main/resources/fabric.mod.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "toolbox",
|
||||||
|
"version": "${version}",
|
||||||
|
"name": "Altarik Toolbox",
|
||||||
|
"description": "Altarik Toolbox, for developers",
|
||||||
|
"authors": [
|
||||||
|
"Altarik"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"contact": {
|
||||||
|
"homepage": "https://altarik.fr"
|
||||||
|
},
|
||||||
|
"license": "Altarik @ All-Rights-Reserved ",
|
||||||
|
"icon": "assets/toolbox/icon.png",
|
||||||
|
"environment": "*",
|
||||||
|
"depends": {
|
||||||
|
"fabricloader": "^${loaderVersion}",
|
||||||
|
"fabric-api": "*",
|
||||||
|
"minecraft": "${minecraftVersion}",
|
||||||
|
"java": ">=17",
|
||||||
|
"toolbox-core": "${version}",
|
||||||
|
"toolbox-database": "${version}",
|
||||||
|
"toolbox-pagination": "${version}",
|
||||||
|
"toolbox-task": "${version}"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"modmenu": {
|
||||||
|
"badges": [ "library" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user